relx 0.1.0
A Modern C++23 Type-Safe SQL Query Builder
Loading...
Searching...
No Matches
function.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "core.hpp"
5#include "value.hpp"
6
7#include <iostream>
8#include <memory>
9#include <string>
10#include <vector>
11
12namespace relx::query {
13
16template <SqlExpr Expr>
18public:
19 FunctionExpr(std::string name, Expr expr) : func_name_(std::move(name)), expr_(std::move(expr)) {}
20
21 std::string to_sql() const override { return func_name_ + "(" + expr_.to_sql() + ")"; }
22
23 std::vector<std::string> bind_params() const override { return expr_.bind_params(); }
24
25 std::string column_name() const override { return func_name_ + "(" + expr_.column_name() + ")"; }
26
27 std::string table_name() const override {
28 if constexpr (std::is_base_of_v<ColumnExpression, Expr>) {
29 // Propagate table name from the expression if it's a column expression
30 return expr_.table_name();
31 } else {
32 return "";
33 }
34 }
35
36private:
37 std::string func_name_;
38 Expr expr_;
39};
40
43public:
44 explicit NullaryFunctionExpr(std::string name) : func_name_(std::move(name)) {}
45
46 std::string to_sql() const override { return func_name_ + "()"; }
47
48 std::vector<std::string> bind_params() const override { return {}; }
49
50 std::string column_name() const override { return func_name_ + "()"; }
51
52 std::string table_name() const override { return ""; }
53
54private:
55 std::string func_name_;
56};
57
60public:
61 std::string to_sql() const override { return "COUNT(*)"; }
62
63 std::vector<std::string> bind_params() const override { return {}; }
64
65 std::string column_name() const override { return "COUNT(*)"; }
66
67 std::string table_name() const override { return ""; }
68};
69
72inline auto count_all() {
73 return CountAllExpr{};
74}
75
80template <SqlExpr Expr>
81auto count(Expr expr) {
82 return FunctionExpr<Expr>("COUNT", std::move(expr));
83}
84
85// Overload for column types
86template <typename T>
87 requires ColumnType<T>
88auto count(const T& column) {
89 return count(to_expr(column));
90}
91
96template <SqlExpr Expr>
97auto count_distinct(Expr expr) {
98 return count(distinct(std::move(expr)));
99}
100
101// Overload for column types
102template <typename T>
103 requires ColumnType<T>
104auto count_distinct(const T& column) {
106}
107
109namespace aggregate_checking {
110
112template <typename T>
113concept Summable = std::is_arithmetic_v<std::remove_cvref_t<T>> &&
114 !std::same_as<std::remove_cvref_t<T>, bool>;
115
117template <typename T>
118concept Countable = true;
119
121template <typename T>
122concept Comparable = std::is_arithmetic_v<std::remove_cvref_t<T>> ||
123 std::same_as<std::remove_cvref_t<T>, std::string> ||
124 std::same_as<std::remove_cvref_t<T>, std::string_view>;
125
127template <typename T>
129 using type = T;
130};
131
132template <typename TableT, schema::fixed_string Name, typename ColumnT, typename... Modifiers>
133struct extract_column_type<schema::column<TableT, Name, ColumnT, Modifiers...>> {
134 using type = ColumnT;
135};
136
137template <typename T>
139} // namespace aggregate_checking
140
145template <SqlExpr Expr>
146auto sum(Expr expr) {
147 return FunctionExpr<Expr>("SUM", std::move(expr));
148}
149
150// Overload for column types with type checking
151template <typename T>
152 requires ColumnType<T>
153auto sum(const T& column) {
156 "SUM can only be used with numeric columns (int, long, float, double, etc.). "
157 "Boolean columns are not summable.");
158 return sum(to_expr(column));
159}
160
165template <SqlExpr Expr>
166auto avg(Expr expr) {
167 return FunctionExpr<Expr>("AVG", std::move(expr));
168}
169
170// Overload for column types with type checking
171template <typename T>
172 requires ColumnType<T>
173auto avg(const T& column) {
176 "AVG can only be used with numeric columns (int, long, float, double, etc.). "
177 "Boolean and string columns cannot be averaged.");
178 return avg(to_expr(column));
179}
180
185template <SqlExpr Expr>
186auto min(Expr expr) {
187 return FunctionExpr<Expr>("MIN", std::move(expr));
188}
189
190// Overload for column types with type checking
191template <typename T>
192 requires ColumnType<T>
193auto min(const T& column) {
196 "MIN can only be used with comparable columns (numeric types or strings). "
197 "Boolean columns cannot be compared for MIN/MAX.");
198 return min(to_expr(column));
199}
200
205template <SqlExpr Expr>
206auto max(Expr expr) {
207 return FunctionExpr<Expr>("MAX", std::move(expr));
208}
209
210// Overload for column types with type checking
211template <typename T>
212 requires ColumnType<T>
213auto max(const T& column) {
216 "MAX can only be used with comparable columns (numeric types or strings). "
217 "Boolean columns cannot be compared for MIN/MAX.");
218 return max(to_expr(column));
219}
220
225template <SqlExpr Expr>
227public:
228 explicit DistinctExpr(Expr expr) : expr_(std::move(expr)) {}
229
230 std::string to_sql() const override { return "DISTINCT " + expr_.to_sql(); }
231
232 std::vector<std::string> bind_params() const override { return expr_.bind_params(); }
233
234 std::string column_name() const override {
235 if constexpr (std::is_base_of_v<ColumnExpression, Expr>) {
236 return "DISTINCT_" + expr_.column_name();
237 } else {
238 return "DISTINCT_EXPR";
239 }
240 }
241
242 std::string table_name() const override {
243 if constexpr (std::is_base_of_v<ColumnExpression, Expr>) {
244 return expr_.table_name();
245 } else {
246 return "";
247 }
248 }
249
250private:
251 Expr expr_;
252};
253
258template <SqlExpr Expr>
259auto distinct(Expr expr) {
260 return DistinctExpr<Expr>(std::move(expr));
261}
262
263// Overload for column types
264template <typename T>
265 requires ColumnType<T>
266auto distinct(const T& column) {
267 return distinct(to_expr(column));
268}
269
274template <SqlExpr Expr>
275auto lower(Expr expr) {
276 return FunctionExpr<Expr>("LOWER", std::move(expr));
277}
278
279// Overload for column types with type checking
280template <typename T>
281 requires ColumnType<T>
282auto lower(const T& column) {
284 static_assert(
285 std::same_as<column_type, std::string> || std::same_as<column_type, std::string_view> ||
286 std::same_as<column_type, const char*>,
287 "LOWER can only be used with string columns (std::string, std::string_view, const char*). "
288 "Numeric and boolean columns cannot be converted to lowercase.");
289 return lower(to_expr(column));
290}
291
296template <SqlExpr Expr>
297auto upper(Expr expr) {
298 return FunctionExpr<Expr>("UPPER", std::move(expr));
299}
300
301// Overload for column types with type checking
302template <typename T>
303 requires ColumnType<T>
304auto upper(const T& column) {
306 static_assert(
307 std::same_as<column_type, std::string> || std::same_as<column_type, std::string_view> ||
308 std::same_as<column_type, const char*>,
309 "UPPER can only be used with string columns (std::string, std::string_view, const char*). "
310 "Numeric and boolean columns cannot be converted to uppercase.");
311 return upper(to_expr(column));
312}
313
318template <SqlExpr Expr>
319auto length(Expr expr) {
320 return FunctionExpr<Expr>("LENGTH", std::move(expr));
321}
322
323// Overload for column types
324template <typename T>
325 requires ColumnType<T>
326auto length(const T& column) {
327 return length(to_expr(column));
328}
329
334template <SqlExpr Expr>
335auto trim(Expr expr) {
336 return FunctionExpr<Expr>("TRIM", std::move(expr));
337}
338
339// Overload for column types
340template <typename T>
341 requires ColumnType<T>
342auto trim(const T& column) {
343 return trim(to_expr(column));
344}
345
354template <SqlExpr First, SqlExpr Second, SqlExpr... Rest>
356public:
357 CoalesceExpr(First first, Second second, Rest... rest)
358 : first_(std::move(first)), second_(std::move(second)),
359 rest_(std::make_tuple(std::move(rest)...)) {}
360
361 std::string to_sql() const override {
362 std::stringstream ss;
363 ss << "COALESCE(" << first_.to_sql() << ", " << second_.to_sql();
364
365 std::apply([&](const auto&... items) { ((ss << ", " << items.to_sql()), ...); }, rest_);
366
367 ss << ")";
368 return ss.str();
369 }
370
371 std::vector<std::string> bind_params() const override {
372 std::vector<std::string> params;
373
374 auto first_params = first_.bind_params();
375 params.insert(params.end(), first_params.begin(), first_params.end());
376
377 auto second_params = second_.bind_params();
378 params.insert(params.end(), second_params.begin(), second_params.end());
379
380 std::apply(
381 [&](const auto&... items) {
382 ((params.insert(params.end(), items.bind_params().begin(), items.bind_params().end())),
383 ...);
384 },
385 rest_);
386
387 return params;
388 }
389
390 std::string column_name() const override { return "COALESCE"; }
391
392 std::string table_name() const override { return ""; }
393
394private:
395 First first_;
396 Second second_;
397 std::tuple<Rest...> rest_;
398};
399
408template <SqlExpr First, SqlExpr Second, SqlExpr... Rest>
409auto coalesce(First first, Second second, Rest... rest) {
410 return CoalesceExpr<First, Second, Rest...>(std::move(first), std::move(second),
411 std::move(rest)...);
412}
413
414// Overload for first argument as column type
415template <typename T, SqlExpr Second, SqlExpr... Rest>
416 requires ColumnType<T>
417auto coalesce(const T& column, Second second, Rest... rest) {
418 return coalesce(to_expr(column), std::move(second), std::move(rest)...);
419}
420
421// Overload for first and second arguments as column types
422template <typename T1, typename T2, SqlExpr... Rest>
423 requires ColumnType<T1> && ColumnType<T2>
424auto coalesce(const T1& column1, const T2& column2, Rest... rest) {
425 return coalesce(to_expr(column1), to_expr(column2), std::move(rest)...);
426}
427
428// Overload for column and string literal
429template <typename T>
430 requires ColumnType<T>
431auto coalesce(const T& column, const char* str) {
432 return coalesce(to_expr(column), val(str));
433}
434
435// Overload for column and std::string
436template <typename T>
437 requires ColumnType<T>
438auto coalesce(const T& column, const std::string& str) {
439 return coalesce(to_expr(column), val(str));
440}
441
442// Overload for column, column, and string literal
443template <typename T1, typename T2>
444 requires ColumnType<T1> && ColumnType<T2>
445auto coalesce(const T1& column1, const T2& column2, const char* str) {
446 return coalesce(to_expr(column1), to_expr(column2), val(str));
447}
448
449// First define CaseExpr class fully
451public:
452 using WhenThenPair = std::pair<std::unique_ptr<SqlExpression>, std::unique_ptr<SqlExpression>>;
453
454 explicit CaseExpr(std::vector<WhenThenPair>&& when_thens,
455 std::unique_ptr<SqlExpression> else_expr)
456 : when_thens_(std::move(when_thens)), else_expr_(std::move(else_expr)) {}
457
458 // Allow move operations
459 CaseExpr(CaseExpr&&) = default;
461
462 // Disable copy operations (due to unique_ptr)
463 CaseExpr(const CaseExpr&) = delete;
464 CaseExpr& operator=(const CaseExpr&) = delete;
465
466 std::string to_sql() const override {
467 std::string sql = "CASE";
468
469 for (const auto& [when_cond, then_val] : when_thens_) {
470 sql += " WHEN (";
471 std::string cond = when_cond->to_sql();
472 // Remove any existing outer parentheses to avoid double parentheses
473 if (cond.size() >= 2 && cond.front() == '(' && cond.back() == ')') {
474 cond = cond.substr(1, cond.size() - 2);
475 }
476 sql += cond + ") THEN " + then_val->to_sql();
477 }
478
479 if (else_expr_) {
480 sql += " ELSE " + else_expr_->to_sql();
481 }
482
483 sql += " END";
484 return sql;
485 }
486
487 std::vector<std::string> bind_params() const override {
488 std::vector<std::string> params;
489
490 // Interleave condition and value parameters in the expected order
491 for (const auto& [when_cond, then_val] : when_thens_) {
492 // First condition parameters
493 auto when_params = when_cond->bind_params();
494 params.insert(params.end(), when_params.begin(), when_params.end());
495
496 // Then the value parameters
497 auto then_params = then_val->bind_params();
498 params.insert(params.end(), then_params.begin(), then_params.end());
499 }
500
501 // Finally collect ELSE parameters if present
502 if (else_expr_) {
503 auto else_params = else_expr_->bind_params();
504 params.insert(params.end(), else_params.begin(), else_params.end());
505 }
506
507 return params;
508 }
509
510 std::string column_name() const override { return "CASE"; }
511
512 std::string table_name() const override { return ""; }
513
514private:
515 std::vector<WhenThenPair> when_thens_;
516 std::unique_ptr<SqlExpression> else_expr_;
517};
518
519// Then define CaseBuilder that uses CaseExpr with type checking
520template <typename ResultType = void>
522public:
523 using WhenThenPair = std::pair<std::unique_ptr<SqlExpression>, std::unique_ptr<SqlExpression>>;
524
525private:
526 std::vector<WhenThenPair> when_thens_;
527 std::unique_ptr<SqlExpression> else_expr_;
528
529public:
530 TypedCaseBuilder() : when_thens_(), else_expr_(nullptr) {}
531
532 // Move constructor
534 : when_thens_(std::move(other.when_thens_)), else_expr_(std::move(other.else_expr_)) {}
535
536 // Move assignment
538 if (this != &other) {
539 when_thens_ = std::move(other.when_thens_);
540 else_expr_ = std::move(other.else_expr_);
541 }
542 return *this;
543 }
544
545 // Delete copy constructor and assignment to prevent accidental copies
548
549 // Template constructor for converting between different result types
550 template <typename OtherType>
552 : when_thens_(std::move(other.when_thens_)), else_expr_(std::move(other.else_expr_)) {}
553
554 template <SqlExpr Then>
555 auto when(const ConditionExpr auto& when_cond, const Then& then) {
556 if constexpr (std::same_as<ResultType, void>) {
557 // First WHEN clause - establish the result type
558 when_thens_.emplace_back(
559 std::make_unique<std::remove_cvref_t<decltype(when_cond)>>(when_cond),
560 std::make_unique<Then>(then));
561 return TypedCaseBuilder<Then>(std::move(*this));
562 } else {
563 // Subsequent WHEN clauses - ensure type compatibility
564 static_assert(std::same_as<std::remove_cvref_t<Then>, std::remove_cvref_t<ResultType>>,
565 "All WHEN/THEN branches in CASE expressions must return the same type. "
566 "Mixed types in CASE branches can lead to runtime type errors.");
567
568 when_thens_.emplace_back(
569 std::make_unique<std::remove_cvref_t<decltype(when_cond)>>(when_cond),
570 std::make_unique<Then>(then));
571 return std::move(*this);
572 }
573 }
574
575 // Specific overloads for common literal types to avoid template conflicts
576 auto when(const ConditionExpr auto& when_cond, const char* then) {
577 return this->when(when_cond, query::val(then));
578 }
579
580 auto when(const ConditionExpr auto& when_cond, const std::string& then) {
581 return this->when(when_cond, query::val(then));
582 }
583
584 auto when(const ConditionExpr auto& when_cond, int then) {
585 return this->when(when_cond, query::val(then));
586 }
587
588 auto when(const ConditionExpr auto& when_cond, long then) {
589 return this->when(when_cond, query::val(then));
590 }
591
592 auto when(const ConditionExpr auto& when_cond, double then) {
593 return this->when(when_cond, query::val(then));
594 }
595
596 auto when(const ConditionExpr auto& when_cond, float then) {
597 return this->when(when_cond, query::val(then));
598 }
599
600 auto when(const ConditionExpr auto& when_cond, bool then) {
601 return this->when(when_cond, query::val(then));
602 }
603
604 template <SqlExpr Else>
605 // NOLINTNEXTLINE(readability-identifier-naming)
606 auto else_(const Else& else_expr) {
607 if constexpr (!std::same_as<ResultType, void>) {
608 static_assert(std::same_as<std::remove_cvref_t<Else>, std::remove_cvref_t<ResultType>>,
609 "ELSE clause type must match the WHEN/THEN branch types in CASE expressions.");
610 }
611
612 else_expr_ = std::make_unique<Else>(else_expr);
613 return std::move(*this);
614 }
615
616 // Specific overloads for common literal types in else clause
617 // Have to use underscore to avoid conflict with else keyword
618 // but this is not allowed by clang-tidy.
619 // NOLINTNEXTLINE(readability-identifier-naming)
620 auto else_(const char* else_value) { return this->else_(query::val(else_value)); }
621
622 // NOLINTNEXTLINE(readability-identifier-naming)
623 auto else_(const std::string& else_value) { return this->else_(query::val(else_value)); }
624
625 // NOLINTNEXTLINE(readability-identifier-naming)
626 auto else_(int else_value) { return this->else_(query::val(else_value)); }
627
628 // NOLINTNEXTLINE(readability-identifier-naming)
629 auto else_(long else_value) { return this->else_(query::val(else_value)); }
630
631 // NOLINTNEXTLINE(readability-identifier-naming)
632 auto else_(double else_value) { return this->else_(query::val(else_value)); }
633
634 // NOLINTNEXTLINE(readability-identifier-naming)
635 auto else_(float else_value) { return this->else_(query::val(else_value)); }
636
637 // NOLINTNEXTLINE(readability-identifier-naming)
638 auto else_(bool else_value) { return this->else_(query::val(else_value)); }
639
640 // Build the final CaseExpr
641 auto build() { return CaseExpr(std::move(when_thens_), std::move(else_expr_)); }
642
643 // Friend access for template constructor
644 template <typename>
645 friend class TypedCaseBuilder;
646};
647
648// Keep the old CaseBuilder as an alias for backward compatibility
650
653// NOLINTNEXTLINE(readability-identifier-naming)
654inline auto case_() {
655 return TypedCaseBuilder<void>();
656}
657
658// Specialized as function for CaseExpr
659inline auto as(CaseExpr&& expr, std::string alias) {
660 // Create a shared_ptr directly with an in-place construction
661 // This avoids the need to copy the CaseExpr
662 auto expr_ptr = std::make_unique<CaseExpr>(std::move(expr));
663 return AliasedColumn<CaseExpr>(std::shared_ptr<CaseExpr>(std::move(expr_ptr)), std::move(alias));
664}
665
666} // namespace relx::query
std::string to_sql() const override
Definition function.hpp:466
CaseExpr & operator=(CaseExpr &&)=default
std::string table_name() const override
Definition function.hpp:512
std::pair< std::unique_ptr< SqlExpression >, std::unique_ptr< SqlExpression > > WhenThenPair
Definition function.hpp:452
CaseExpr(CaseExpr &&)=default
std::vector< std::string > bind_params() const override
Definition function.hpp:487
CaseExpr & operator=(const CaseExpr &)=delete
std::string column_name() const override
Definition function.hpp:510
CaseExpr(const CaseExpr &)=delete
CaseExpr(std::vector< WhenThenPair > &&when_thens, std::unique_ptr< SqlExpression > else_expr)
Definition function.hpp:454
COALESCE function.
Definition function.hpp:355
std::vector< std::string > bind_params() const override
Definition function.hpp:371
std::string table_name() const override
Definition function.hpp:392
CoalesceExpr(First first, Second second, Rest... rest)
Definition function.hpp:357
std::string to_sql() const override
Definition function.hpp:361
std::string column_name() const override
Definition function.hpp:390
Base class for column expressions.
Expression representing COUNT(*) in SQL.
Definition function.hpp:59
std::string to_sql() const override
Definition function.hpp:61
std::vector< std::string > bind_params() const override
Definition function.hpp:63
std::string table_name() const override
Definition function.hpp:67
std::string column_name() const override
Definition function.hpp:65
DISTINCT qualifier for an expression.
Definition function.hpp:226
std::string table_name() const override
Definition function.hpp:242
std::string to_sql() const override
Definition function.hpp:230
std::vector< std::string > bind_params() const override
Definition function.hpp:232
std::string column_name() const override
Definition function.hpp:234
Base class for SQL function expressions.
Definition function.hpp:17
FunctionExpr(std::string name, Expr expr)
Definition function.hpp:19
std::string to_sql() const override
Definition function.hpp:21
std::string column_name() const override
Definition function.hpp:25
std::string table_name() const override
Definition function.hpp:27
std::vector< std::string > bind_params() const override
Definition function.hpp:23
Base class for SQL function expressions with no arguments.
Definition function.hpp:42
std::string table_name() const override
Definition function.hpp:52
std::string to_sql() const override
Definition function.hpp:46
NullaryFunctionExpr(std::string name)
Definition function.hpp:44
std::string column_name() const override
Definition function.hpp:50
std::vector< std::string > bind_params() const override
Definition function.hpp:48
auto when(const ConditionExpr auto &when_cond, const Then &then)
Definition function.hpp:555
auto when(const ConditionExpr auto &when_cond, int then)
Definition function.hpp:584
std::pair< std::unique_ptr< SqlExpression >, std::unique_ptr< SqlExpression > > WhenThenPair
Definition function.hpp:523
auto when(const ConditionExpr auto &when_cond, const char *then)
Definition function.hpp:576
TypedCaseBuilder(TypedCaseBuilder &&other) noexcept
Definition function.hpp:533
auto else_(int else_value)
Definition function.hpp:626
auto else_(const Else &else_expr)
Definition function.hpp:606
TypedCaseBuilder & operator=(TypedCaseBuilder &&other) noexcept
Definition function.hpp:537
auto else_(const std::string &else_value)
Definition function.hpp:623
auto else_(float else_value)
Definition function.hpp:635
TypedCaseBuilder & operator=(const TypedCaseBuilder &)=delete
TypedCaseBuilder(const TypedCaseBuilder &)=delete
auto else_(bool else_value)
Definition function.hpp:638
auto when(const ConditionExpr auto &when_cond, float then)
Definition function.hpp:596
auto when(const ConditionExpr auto &when_cond, const std::string &then)
Definition function.hpp:580
auto when(const ConditionExpr auto &when_cond, double then)
Definition function.hpp:592
auto when(const ConditionExpr auto &when_cond, bool then)
Definition function.hpp:600
auto when(const ConditionExpr auto &when_cond, long then)
Definition function.hpp:588
auto else_(long else_value)
Definition function.hpp:629
TypedCaseBuilder(TypedCaseBuilder< OtherType > &&other)
Definition function.hpp:551
auto else_(const char *else_value)
Definition function.hpp:620
auto else_(double else_value)
Definition function.hpp:632
Represents a column in a database table.
Definition column.hpp:217
Concept for a condition expression.
Definition core.hpp:58
Concept for comparable types suitable for MIN/MAX.
Definition function.hpp:122
Concept for types suitable for COUNT (any type is countable)
Definition function.hpp:118
Concept for numeric types suitable for SUM and AVG.
Definition function.hpp:113
typename extract_column_type< T >::type extract_column_type_t
Definition function.hpp:138
auto min(Expr expr)
MIN aggregate function.
Definition function.hpp:186
auto second(const T &date_column)
Get the second from a timestamp.
Definition date.hpp:772
auto avg(Expr expr)
AVG aggregate function.
Definition function.hpp:166
auto trim(Expr expr)
TRIM string function.
Definition function.hpp:335
auto max(Expr expr)
MAX aggregate function.
Definition function.hpp:206
auto sum(Expr expr)
SUM aggregate function.
Definition function.hpp:146
auto length(Expr expr)
LENGTH string function.
Definition function.hpp:319
auto as(const Expr &expr, std::string alias)
Create an aliased column expression.
auto case_()
Create a CASE expression with type checking.
Definition function.hpp:654
auto to_expr(const C &col, std::string_view table_name="")
Helper to wrap a schema column in a SQL expression.
auto count_distinct(Expr expr)
COUNT(DISTINCT expr) aggregate function.
Definition function.hpp:97
auto count(Expr expr)
COUNT aggregate function.
Definition function.hpp:81
auto coalesce(First first, Second second, Rest... rest)
Create a COALESCE expression.
Definition function.hpp:409
auto lower(Expr expr)
LOWER string function.
Definition function.hpp:275
auto distinct(Expr expr)
Create a DISTINCT expression.
Definition function.hpp:259
auto val(const char *str)
Helper to create a value expression from a string literal.
Definition value.hpp:127
auto upper(Expr expr)
UPPER string function.
Definition function.hpp:297
auto count_all()
COUNT(*) aggregate function.
Definition function.hpp:72
STL namespace.
Helper to extract column type for checking.
Definition function.hpp:128
Compile-time string type that can be used as template non-type parameter in C++20.