relx 0.1.0
A Modern C++23 Type-Safe SQL Query Builder
Loading...
Searching...
No Matches
select.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "condition.hpp"
5#include "core.hpp"
6#include "meta.hpp"
8#include "value.hpp"
9
10#include <iostream>
11#include <memory>
12#include <optional>
13#include <sstream>
14#include <string>
15#include <tuple>
16#include <type_traits>
17#include <utility>
18#include <vector>
19
20namespace relx::query {
21
31template <TableType Table, ConditionExpr Condition>
32struct JoinSpec {
33 Table table;
34 Condition condition;
36};
37
42template <ConditionExpr Condition>
43auto on(Condition cond) {
44 return cond;
45}
46
58template <typename Columns, typename Tables = std::tuple<>, typename Joins = std::tuple<>,
59 typename Where = std::nullopt_t, typename GroupBys = std::tuple<>,
60 typename OrderBys = std::tuple<>, typename HavingCond = std::nullopt_t,
61 typename LimitVal = std::nullopt_t, typename OffsetVal = std::nullopt_t,
62 bool IsDistinct = false>
64private:
65public:
66 using columns_type = Columns;
67 using tables_type = Tables;
68 using joins_type = Joins;
69 using where_type = Where;
70 using group_bys_type = GroupBys;
71 using order_bys_type = OrderBys;
72 using having_type = HavingCond;
73 using limit_type = LimitVal;
74 using offset_type = OffsetVal;
75
86 explicit SelectQuery(Columns columns, Tables tables = {}, Joins joins = {},
87 Where where = std::nullopt, GroupBys group_bys = {}, OrderBys order_bys = {},
88 HavingCond having = std::nullopt, LimitVal limit = std::nullopt,
89 OffsetVal offset = std::nullopt)
90 : columns_(std::move(columns)), tables_(std::move(tables)), joins_(std::move(joins)),
91 where_(std::move(where)), group_bys_(std::move(group_bys)),
92 order_bys_(std::move(order_bys)), having_(std::move(having)), limit_(std::move(limit)),
93 offset_(std::move(offset)) {}
94
97 std::string to_sql() const {
98 std::stringstream ss;
99 ss << "SELECT ";
100
101 // Add DISTINCT if enabled
102 if constexpr (IsDistinct) {
103 ss << "DISTINCT ";
104 }
105
106 // Add columns
107 ss << tuple_to_sql(columns_, ", ");
108
109 // Add FROM clause if tables are specified
110 if constexpr (!is_empty_tuple<Tables>()) {
111 ss << " FROM ";
112 int i = 0;
113 std::apply(
114 [&](const auto&... tables) { ((ss << (i++ > 0 ? ", " : "") << tables.table_name), ...); },
115 tables_);
116 }
117
118 // Add JOINs
119 if constexpr (!is_empty_tuple<Joins>()) {
121 [&](const auto& join) {
122 ss << " ";
123
124 switch (join.type) {
125 case JoinType::Inner:
126 ss << "JOIN ";
127 break;
128 case JoinType::Left:
129 ss << "LEFT JOIN ";
130 break;
131 case JoinType::Right:
132 ss << "RIGHT JOIN ";
133 break;
134 case JoinType::Full:
135 ss << "FULL JOIN ";
136 break;
137 case JoinType::Cross:
138 ss << "CROSS JOIN ";
139 break;
140 }
141
142 ss << join.table.table_name;
143
144 if (join.type != JoinType::Cross) {
145 ss << " ON ";
146 ss << join.condition.to_sql();
147 }
148 },
149 joins_);
150 }
151
152 // Add WHERE clause
153 if constexpr (!std::is_same_v<Where, std::nullopt_t>) {
154 if (where_.has_value()) {
155 ss << " WHERE " << where_.value().to_sql();
156 }
157 }
158
159 // Add GROUP BY clause
160 if constexpr (!is_empty_tuple<GroupBys>()) {
161 ss << " GROUP BY " << tuple_to_sql(group_bys_, ", ");
162 }
163
164 // Add HAVING clause
165 if constexpr (!std::is_same_v<HavingCond, std::nullopt_t>) {
166 if (having_.has_value()) {
167 ss << " HAVING " << having_.value().to_sql();
168 }
169 }
170
171 // Add ORDER BY clause
172 if constexpr (!is_empty_tuple<OrderBys>()) {
173 ss << " ORDER BY " << tuple_to_sql(order_bys_, ", ");
174 }
175
176 // Add LIMIT clause
177 if constexpr (!std::is_same_v<LimitVal, std::nullopt_t>) {
178 if constexpr (std::is_same_v<LimitVal, Value<int>>) {
179 ss << " LIMIT " << limit_.to_sql();
180 } else if (limit_.has_value()) {
181 ss << " LIMIT " << limit_.value().to_sql();
182 }
183 }
184
185 // Add OFFSET clause
186 if constexpr (!std::is_same_v<OffsetVal, std::nullopt_t>) {
187 if constexpr (std::is_same_v<OffsetVal, Value<int>>) {
188 ss << " OFFSET " << offset_.to_sql();
189 } else if (offset_.has_value()) {
190 ss << " OFFSET " << offset_.value().to_sql();
191 }
192 }
193
194 return ss.str();
195 }
196
199 std::vector<std::string> bind_params() const {
200 std::vector<std::string> params;
201
202 // Collect parameters from columns
203 if constexpr (!is_empty_tuple<Columns>()) {
204 auto column_params = tuple_bind_params(columns_);
205 params.insert(params.end(), column_params.begin(), column_params.end());
206 }
207
208 // Tables don't have bind parameters, so we skip collecting them
209
210 // Collect parameters from joins
211 if constexpr (!is_empty_tuple<Joins>()) {
212 std::apply(
213 [&](const auto&... joins) {
214 (([&]() {
215 try {
216 auto join_params = joins.condition.bind_params();
217 params.insert(params.end(), join_params.begin(), join_params.end());
218 } catch (const std::exception& e) {
219 // Log error and continue
220 std::print("Error collecting join params: {}", e.what());
221 }
222 })(),
223 ...);
224 },
225 joins_);
226 }
227
228 // Collect where parameter
229 if constexpr (!std::is_same_v<Where, std::nullopt_t>) {
230 if (where_.has_value()) {
231 auto where_params = where_.value().bind_params();
232 params.insert(params.end(), where_params.begin(), where_params.end());
233 }
234 }
235
236 // Collect group by parameters
237 if constexpr (!is_empty_tuple<GroupBys>()) {
238 auto group_params = tuple_bind_params(group_bys_);
239 params.insert(params.end(), group_params.begin(), group_params.end());
240 }
241
242 // Collect having parameter
243 if constexpr (!std::is_same_v<HavingCond, std::nullopt_t>) {
244 if (having_.has_value()) {
245 auto having_params = having_.value().bind_params();
246 params.insert(params.end(), having_params.begin(), having_params.end());
247 }
248 }
249
250 // Collect order by parameters
251 if constexpr (!is_empty_tuple<OrderBys>()) {
252 auto order_params = tuple_bind_params(order_bys_);
253 params.insert(params.end(), order_params.begin(), order_params.end());
254 }
255
256 // Collect limit parameter
257 if constexpr (!std::is_same_v<LimitVal, std::nullopt_t>) {
258 if constexpr (std::is_same_v<LimitVal, Value<int>>) {
259 auto limit_params = limit_.bind_params();
260 params.insert(params.end(), limit_params.begin(), limit_params.end());
261 } else if (limit_.has_value()) {
262 auto limit_params = limit_.value().bind_params();
263 params.insert(params.end(), limit_params.begin(), limit_params.end());
264 }
265 }
266
267 // Collect offset parameter
268 if constexpr (!std::is_same_v<OffsetVal, std::nullopt_t>) {
269 if constexpr (std::is_same_v<OffsetVal, Value<int>>) {
270 auto offset_params = offset_.bind_params();
271 params.insert(params.end(), offset_params.begin(), offset_params.end());
272 } else if (offset_.has_value()) {
273 auto offset_params = offset_.value().bind_params();
274 params.insert(params.end(), offset_params.begin(), offset_params.end());
275 }
276 }
277
278 return params;
279 }
280
285 template <TableType T>
286 auto from(const T& table) const {
287 using new_tables_type = decltype(std::tuple_cat(tables_, std::tuple<T>()));
288 return SelectQuery<Columns, new_tables_type, Joins, Where, GroupBys, OrderBys, HavingCond,
289 LimitVal, OffsetVal, IsDistinct>(
290 columns_, std::tuple_cat(tables_, std::tuple<T>(table)), joins_, where_, group_bys_,
291 order_bys_, having_, limit_, offset_);
292 }
293
298 template <TableType... Args>
299 auto from(const Args&... tables) const {
300 using new_tables_type = decltype(std::tuple_cat(tables_, std::tuple<Args...>()));
301 return SelectQuery<Columns, new_tables_type, Joins, Where, GroupBys, OrderBys, HavingCond,
302 LimitVal, OffsetVal, IsDistinct>(
303 columns_, std::tuple_cat(tables_, std::tuple<Args...>(tables...)), joins_, where_,
304 group_bys_, order_bys_, having_, limit_, offset_);
305 }
306
314 template <TableType Table, ConditionExpr Condition>
315 auto join(const Table& table, const Condition& cond, JoinType type = JoinType::Inner) const {
317 using new_joins_type = decltype(std::tuple_cat(
318 joins_, std::tuple<JoinSpec>{JoinSpec{table, cond, type}}));
319
320 return SelectQuery<Columns, Tables, new_joins_type, Where, GroupBys, OrderBys, HavingCond,
321 LimitVal, OffsetVal, IsDistinct>(
322 columns_, tables_,
323 std::tuple_cat(joins_, std::tuple<JoinSpec>{JoinSpec{table, cond, type}}), where_,
324 group_bys_, order_bys_, having_, limit_, offset_);
325 }
326
333 template <TableType Table, ConditionExpr Condition>
334 auto left_join(const Table& table, const Condition& cond) const {
335 return join(table, cond, JoinType::Left);
336 }
337
344 template <TableType Table, ConditionExpr Condition>
345 auto right_join(const Table& table, const Condition& cond) const {
346 return join(table, cond, JoinType::Right);
347 }
348
355 template <TableType Table, ConditionExpr Condition>
356 auto full_join(const Table& table, const Condition& cond) const {
357 return join(table, cond, JoinType::Full);
358 }
359
364 template <TableType Table>
365 auto cross_join(const Table& table) const {
366 // A cross join doesn't need a condition, so we use a dummy condition
367 struct DummyCondition : public SqlExpression {
368 // TODO Get rid of this dummy conditions somehow
369 std::string to_sql() const override { return "1=1"; }
370 std::vector<std::string> bind_params() const override { return {}; }
371 };
372
373 return join(table, DummyCondition{}, JoinType::Cross);
374 }
375
380 template <ConditionExpr Condition>
381 auto where(const Condition& cond) const {
383 HavingCond, LimitVal, OffsetVal, IsDistinct>(
384 columns_, tables_, joins_, std::optional<Condition>(cond), group_bys_, order_bys_, having_,
385 limit_, offset_);
386 }
387
392 template <typename... Args>
393 auto group_by(const Args&... args) const {
394 auto transform_arg = []<typename T>(const T& arg) {
395 if constexpr (ColumnType<T>) {
396 return to_expr(arg);
397 } else {
398 return arg;
399 }
400 };
401
402 using new_group_bys_type = decltype(std::tuple_cat(
403 group_bys_, std::make_tuple(transform_arg(std::declval<Args>())...)));
404
405 return SelectQuery<Columns, Tables, Joins, Where, new_group_bys_type, OrderBys, HavingCond,
406 LimitVal, OffsetVal, IsDistinct>(
407 columns_, tables_, joins_, where_,
408 std::tuple_cat(group_bys_, std::make_tuple(transform_arg(args)...)), order_bys_, having_,
409 limit_, offset_);
410 }
411
416 template <ConditionExpr Condition>
417 auto having(const Condition& cond) const {
419 LimitVal, OffsetVal, IsDistinct>(
420 columns_, tables_, joins_, where_, group_bys_, order_bys_, std::optional<Condition>(cond),
421 limit_, offset_);
422 }
423
428 template <SqlExpr... Args>
429 auto order_by(const Args&... args) const {
430 using new_order_bys_type = decltype(std::tuple_cat(order_bys_, std::tuple<Args...>{args...}));
431
432 return SelectQuery<Columns, Tables, Joins, Where, GroupBys, new_order_bys_type, HavingCond,
433 LimitVal, OffsetVal, IsDistinct>(
434 columns_, tables_, joins_, where_, group_bys_,
435 std::tuple_cat(order_bys_, std::tuple<Args...>{args...}), having_, limit_, offset_);
436 }
437
442 template <typename T>
443 requires ColumnType<T>
444 auto order_by(const T& column) const {
445 // Type check for ORDER BY - ensure the column is comparable
446 // Extract column type from the column template parameters
447 using ColumnType = typename T::value_type;
448 static_assert(
449 std::is_arithmetic_v<ColumnType> || std::same_as<ColumnType, std::string> ||
450 std::same_as<ColumnType, std::string_view>,
451 "ORDER BY can only be used with comparable columns (numeric types, strings). "
452 "Complex types, boolean columns, or non-comparable types cannot be used for ordering.");
453
454 return order_by(asc(to_expr(column)));
455 }
456
460 auto limit(int limit) const {
461 auto limit_expr = Value<int>(limit);
462
463 return SelectQuery<Columns, Tables, Joins, Where, GroupBys, OrderBys, HavingCond,
464 decltype(limit_expr), OffsetVal, IsDistinct>(
465 columns_, tables_, joins_, where_, group_bys_, order_bys_, having_, std::move(limit_expr),
466 offset_);
467 }
468
472 auto offset(int offset) const {
473 auto offset_expr = Value<int>(offset);
474
475 return SelectQuery<Columns, Tables, Joins, Where, GroupBys, OrderBys, HavingCond, LimitVal,
476 decltype(offset_expr), IsDistinct>(columns_, tables_, joins_, where_,
477 group_bys_, order_bys_, having_, limit_,
478 std::move(offset_expr));
479 }
480
481private:
482 Columns columns_;
483 Tables tables_;
484 Joins joins_;
485 Where where_;
486 GroupBys group_bys_;
487 OrderBys order_bys_;
488 HavingCond having_;
489 LimitVal limit_;
490 OffsetVal offset_;
491};
492
495
500template <typename... Args>
501auto select(const Args&... args) {
502 if constexpr ((ColumnType<Args> && ...)) {
503 // All arguments are columns
504 return SelectQuery(std::tuple<ColumnRef<Args>...>(ColumnRef<Args>(args)...));
505 } else if constexpr ((SqlExpr<Args> && ...)) {
506 // All arguments are expressions
507 return SelectQuery(std::tuple<Args...>(args...));
508 } else {
509 // Mixed columns and expressions
510 // Use a helper function to convert columns to ColumnRef and leave expressions as is
511 auto transform_arg = []<typename T>(const T& arg) {
512 if constexpr (ColumnType<T>) {
513 return ColumnRef<T>(arg);
514 } else {
515 return arg;
516 }
517 };
518
519 return SelectQuery(std::make_tuple(transform_arg(args)...));
520 }
521}
522
527template <typename... Args>
528auto select_expr(const Args&... args) {
529 return select(args...);
530}
531
536template <SqlExpr Expr>
538public:
539 explicit DescendingExpr(Expr expr) : expr_(std::move(expr)) {}
540
541 std::string to_sql() const override { return expr_.to_sql() + " DESC"; }
542
543 std::vector<std::string> bind_params() const override { return expr_.bind_params(); }
544
545private:
546 Expr expr_;
547};
548
553template <SqlExpr Expr>
554auto desc(Expr expr) {
555 return DescendingExpr<Expr>(std::move(expr));
556}
557
558template <typename T>
559 requires ColumnType<T>
560auto desc(const T& column) {
561 return desc(to_expr(column));
562}
563
568template <SqlExpr Expr>
570public:
571 explicit AscendingExpr(Expr expr) : expr_(std::move(expr)) {}
572
573 std::string to_sql() const override { return expr_.to_sql() + " ASC"; }
574
575 std::vector<std::string> bind_params() const override { return expr_.bind_params(); }
576
577private:
578 Expr expr_;
579};
580
585template <SqlExpr Expr>
586auto asc(Expr expr) {
587 return AscendingExpr<Expr>(std::move(expr));
588}
589
594template <typename T>
595 requires ColumnType<T>
596auto asc(const T& column) {
597 return asc(to_expr(column));
598}
599
600// Helper to create a SelectQuery from a list of column expressions
601template <TableType Table>
602auto select_all(const Table& table) {
603 // Create a SelectQuery that selects just *
604 class StarExpression : public SqlExpression {
605 public:
606 std::string to_sql() const override { return "*"; }
607
608 std::vector<std::string> bind_params() const override { return {}; }
609 };
610
611 // Use a star expression for simplicity
612 return SelectQuery(std::tuple<StarExpression>()).from(table);
613}
614
618template <TableType Table>
620 // Create a table instance to work with
621 Table table{};
622 return select_all(table);
623}
624
625// Add new helper functions for DISTINCT queries
626
631template <typename... Args>
632auto select_distinct(const Args&... args) {
633 if constexpr ((ColumnType<Args> && ...)) {
634 // All arguments are columns
635 return SelectQuery<std::tuple<ColumnRef<Args>...>, std::tuple<>, std::tuple<>, std::nullopt_t,
636 std::tuple<>, std::tuple<>, std::nullopt_t, std::nullopt_t, std::nullopt_t,
637 true>(std::tuple<ColumnRef<Args>...>(ColumnRef<Args>(args)...));
638 } else if constexpr ((SqlExpr<Args> && ...)) {
639 // All arguments are expressions
640 return SelectQuery<std::tuple<Args...>, std::tuple<>, std::tuple<>, std::nullopt_t,
641 std::tuple<>, std::tuple<>, std::nullopt_t, std::nullopt_t, std::nullopt_t,
642 true>(std::tuple<Args...>(args...));
643 } else {
644 // Mixed columns and expressions
645 auto transform_arg = []<typename T>(const T& arg) {
646 if constexpr (ColumnType<T>) {
647 return ColumnRef<T>(arg);
648 } else {
649 return arg;
650 }
651 };
652
654 std::tuple<>, std::nullopt_t, std::tuple<>, std::tuple<>, std::nullopt_t,
655 std::nullopt_t, std::nullopt_t, true>(
656 std::make_tuple(transform_arg(args)...));
657 }
658}
659
664template <SqlExpr... Args>
665auto select_distinct_expr(const Args&... args) {
666 return select_distinct(args...);
667}
668
673template <TableType Table>
674auto select_distinct_all(const Table& table) {
675 // Create a SelectQuery that selects DISTINCT *
676 class StarExpression : public SqlExpression {
677 public:
678 std::string to_sql() const override { return "*"; }
679
680 std::vector<std::string> bind_params() const override { return {}; }
681 };
682
683 // Use a star expression with DISTINCT
684 return SelectQuery<std::tuple<StarExpression>, std::tuple<>, std::tuple<>, std::nullopt_t,
685 std::tuple<>, std::tuple<>, std::nullopt_t, std::nullopt_t, std::nullopt_t,
686 true>(std::tuple<StarExpression>())
687 .from(table);
688}
689
693template <TableType Table>
695 // Create a table instance to work with
696 Table table{};
697 return select_distinct_all(table);
698}
699
700} // namespace relx::query
Helper for creating an ascending order by expression.
Definition select.hpp:569
std::vector< std::string > bind_params() const override
Definition select.hpp:575
std::string to_sql() const override
Definition select.hpp:573
Column reference expression.
Helper for creating a descending order by expression.
Definition select.hpp:537
std::vector< std::string > bind_params() const override
Definition select.hpp:543
std::string to_sql() const override
Definition select.hpp:541
Base SELECT query builder.
Definition select.hpp:63
auto cross_join(const Table &table) const
Add a CROSS JOIN clause to the query.
Definition select.hpp:365
auto right_join(const Table &table, const Condition &cond) const
Add a RIGHT JOIN clause to the query.
Definition select.hpp:345
auto limit(int limit) const
Add a LIMIT clause to the query.
Definition select.hpp:460
auto from(const Args &... tables) const
Add a FROM clause with multiple tables.
Definition select.hpp:299
auto where(const Condition &cond) const
Add a WHERE clause to the query.
Definition select.hpp:381
std::string to_sql() const
Generate the SQL for this SELECT query.
Definition select.hpp:97
auto order_by(const T &column) const
Add an ORDER BY clause to the query.
Definition select.hpp:444
auto left_join(const Table &table, const Condition &cond) const
Add a LEFT JOIN clause to the query.
Definition select.hpp:334
SelectQuery(Columns columns, Tables tables={}, Joins joins={}, Where where=std::nullopt, GroupBys group_bys={}, OrderBys order_bys={}, HavingCond having=std::nullopt, LimitVal limit=std::nullopt, OffsetVal offset=std::nullopt)
Constructor for the SELECT query builder.
Definition select.hpp:86
auto full_join(const Table &table, const Condition &cond) const
Add a FULL JOIN clause to the query.
Definition select.hpp:356
std::vector< std::string > bind_params() const
Get the bind parameters for this SELECT query.
Definition select.hpp:199
auto join(const Table &table, const Condition &cond, JoinType type=JoinType::Inner) const
Add a JOIN clause to the query.
Definition select.hpp:315
auto from(const T &table) const
Add a FROM clause to the query.
Definition select.hpp:286
auto having(const Condition &cond) const
Add a HAVING clause to the query.
Definition select.hpp:417
auto order_by(const Args &... args) const
Add an ORDER BY clause to the query.
Definition select.hpp:429
auto group_by(const Args &... args) const
Add a GROUP BY clause to the query.
Definition select.hpp:393
auto offset(int offset) const
Add an OFFSET clause to the query.
Definition select.hpp:472
Represents a literal value in a SQL query.
Definition value.hpp:15
Represents a column in a database table.
Definition column.hpp:217
Concept for column types.
Definition core.hpp:40
Concept for SQL expression components.
Definition core.hpp:29
Concept for database table types.
Definition core.hpp:36
std::string tuple_to_sql(const Tuple &tuple, const char *separator)
Helper to convert a tuple of expressions to SQL.
Definition meta.hpp:20
auto asc(Expr expr)
Create an ascending order by expression.
Definition select.hpp:586
JoinType
Types of JOIN operations.
Definition core.hpp:68
auto desc(Expr expr)
Create a descending order by expression.
Definition select.hpp:554
auto select_expr(const schema::column< TableT, Name, T, Modifiers... > &col, Args &&... args)
static void apply_tuple(Func &&func, const Tuple &tuple)
Helper to apply a function to each element of a tuple.
Definition meta.hpp:52
auto select_distinct(const Args &... args)
Create a SELECT DISTINCT query with the specified columns or expressions.
Definition select.hpp:632
auto select_distinct_all()
Create a SELECT DISTINCT * query without requiring a table instance.
Definition select.hpp:694
auto to_expr(const C &col, std::string_view table_name="")
Helper to wrap a schema column in a SQL expression.
auto select(const Args &... args)
Create a column reference from a member pointer without requiring a table instance.
Definition select.hpp:501
std::vector< std::string > tuple_bind_params(const Tuple &tuple)
Helper to collect bind parameters from a tuple of expressions.
Definition meta.hpp:31
auto from(const SelectQuery< Columns > &query, const Table &table)
FROM extension for schema tables with automatic adapter creation.
Definition helpers.hpp:24
auto select_all()
Create a SELECT * query without requiring a table instance.
Definition select.hpp:619
auto on(Condition cond)
Create a join condition with the ON clause.
Definition select.hpp:43
auto select_distinct_expr(const Args &... args)
Create a SELECT DISTINCT query with the specified column expressions.
Definition select.hpp:665
STL namespace.
relx Query Module
Definition select.hpp:32
Condition condition
Definition select.hpp:34
Base class for SQL expressions.
Definition core.hpp:61