diff --git a/README.md b/README.md index 7155573..7655f26 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,17 @@ const auto query = read> | const auto query = read> | where("age"_c == "Homer"); +// Compile-time error: "age" must be aggregated or included in GROUP BY +const auto query = select_from( + "last_name"_c, + "age"_c +) | group_by("last_name"_c); + +// Compile-time error: Cannot add string and int +const auto query = select_from( + "last_name"_c + "age"_c +); + // Runtime protection against SQL injection std::vector get_people(const auto& conn, const sqlgen::AlphaNumeric& first_name) { diff --git a/docs/README.md b/docs/README.md index 93a9081..de41ca5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,6 +26,7 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab - [sqlgen::group_by and Aggregations](group_by_and_aggregations.md) - How generate GROUP BY queries and aggregate data - [sqlgen::insert](insert.md) - How to insert data within transactions - [sqlgen::update](update.md) - How to update data in a table +- [Other Operations and Functions](other_operations.md) - How to use SQL functions like `coalesce`, `concat`, `abs`, `cast`, and more ## Data Types and Validation diff --git a/docs/other_operations.md b/docs/other_operations.md new file mode 100644 index 0000000..5b993cb --- /dev/null +++ b/docs/other_operations.md @@ -0,0 +1,249 @@ +# Other Operations and Functions + +The `sqlgen` library provides a rich set of SQL operations and functions that can be used in a type-safe and composable way within C++ queries. These operations cover mathematical, string, type conversion, and null-handling functions, and are designed to closely mirror SQL's expressive power. + +## Usage + +You can use these functions in your `select_from` queries, often in combination with column expressions, literals, and other operations. All functions are available in the `sqlgen` namespace. + +--- + +## Mathematical Functions + +### `abs` +Returns the absolute value of a numeric expression. + +```cpp +abs("age"_c * (-1)) | as<"abs_age"> +``` + +### `ceil` / `floor` +Rounds a numeric value up (`ceil`) or down (`floor`) to the nearest integer. + +```cpp +ceil("salary"_c) | as<"salary_ceiled"> +floor("salary"_c) | as<"salary_floored"> +``` + +### `exp`, `ln`, `log2`, `sqrt` +- `exp(x)`: Exponential function (e^x) +- `ln(x)`: Natural logarithm +- `log2(x)`: Base-2 logarithm +- `sqrt(x)`: Square root + +```cpp +round(exp(cast("age"_c)), 2) | as<"exp_age"> +round(sqrt(cast("age"_c)), 2) | as<"sqrt_age"> +``` + +### `sin`, `cos`, `tan` +Trigonometric functions. + +```cpp +sin("angle"_c) | as<"sin_angle"> +cos("angle"_c) | as<"cos_angle"> +tan("angle"_c) | as<"tan_angle"> +``` + +### `round` +Rounds a numeric value to a specified number of decimal places. + +```cpp +round("price"_c, 2) | as<"rounded_price"> +``` + +--- + +## String Functions + +### `length` +Returns the length of a string. + +```cpp +length(trim("first_name"_c)) | as<"length_first_name"> +``` + +### `lower` / `upper` +Converts a string to lowercase or uppercase. + +```cpp +lower("first_name"_c) | as<"first_name_lower"> +upper("first_name"_c) | as<"first_name_upper"> +``` + +### `ltrim`, `rtrim`, `trim` +Removes whitespace (or a specified character) from the left, right, or both sides of a string. + +```cpp +ltrim("first_name"_c) | as<"ltrimmed_name"> +rtrim("last_name"_c) | as<"rtrimmed_name"> +trim("nickname"_c) | as<"trimmed_nickname"> +// With custom characters: +ltrim("field"_c, "_ ") | as<"ltrimmed_field"> +``` + +### `replace` +Replaces all occurrences of a substring with another substring. + +```cpp +replace("first_name"_c, "Bart", "Hugo") | as<"first_name_replaced"> +``` + +### `concat` +Concatenates multiple strings or expressions. + +```cpp +concat("first_name"_c, " ", "last_name"_c) | as<"full_name"> +concat(upper("last_name"_c), ", ", "first_name"_c) | as<"full_name"> +``` + +--- + +## Type Conversion + +### `cast` +Casts a value to a different type (e.g., int to double). + +```cpp +cast("age"_c) | as<"age_as_double"> +``` + +--- + +## Null Handling + +### `coalesce` +Returns the first non-null value in the argument list. + +```cpp +coalesce("last_name"_c, "none") | as<"last_name_or_none"> +coalesce(upper("last_name"_c), "none") | as<"last_name_or_none"> +``` + +--- + +## Nullable Values + +When using these operations on nullable columns (e.g., `std::optional`), the result will also be nullable if any operand is nullable. For example, adding two `std::optional` columns will yield a `std::optional`. The `coalesce` function is especially useful for providing default values for nullable columns. + +--- + +## Nullability Propagation and `coalesce` Semantics + +### General Nullability Rules + +- **Unary operations** (e.g., `abs`, `upper`, `sqrt`): + - If the operand is nullable (`std::optional`), the result is also nullable. + - If the operand is not nullable, the result is not nullable. +- **Binary or ternary operations** (e.g., `+`, `concat`, `replace`, etc.): + - If *any* operand is nullable, the result is nullable (`std::optional`). + - If *all* operands are non-nullable, the result is non-nullable. +- **Type conversion (`cast`)**: + - If the source is nullable, the result is nullable of the target type. + - If the source is not nullable, the result is not nullable. +- **String operations** (e.g., `concat`, `replace`, `ltrim`, `rtrim`, `trim`): + - If any input is nullable, the result is nullable. + - All string operands must have the same underlying type (checked at compile time). + +### `coalesce` Nullability Semantics + +The `coalesce` function returns the first non-null value from its arguments. Its nullability is determined as follows: + +- If **all** arguments are nullable, the result is nullable (`std::optional`). +- If **any** argument is non-nullable, the result is non-nullable (`T`). +- All arguments must have the same underlying type (ignoring nullability), enforced at compile time. + +#### Examples + +```cpp +// All arguments nullable: result is nullable +coalesce(std::optional{}, std::optional{}) // -> std::optional + +// At least one argument non-nullable: result is non-nullable +coalesce(std::optional{}, 42) // -> int +coalesce(42, std::optional{}) // -> int + +// All arguments non-nullable: result is non-nullable +coalesce(1, 2) // -> int + +// Mixed string example +coalesce(std::optional{}, "default") // -> std::string + +// Compile-time error: mismatched types +// coalesce(std::optional{}, std::optional{}) // Error +``` + +#### Practical Usage + +```cpp +// Provide a default for a nullable column +coalesce("last_name"_c, "none") | as<"last_name_or_none"> // Result is std::string +coalesce("middle_name"_c, "nickname"_c) | as<"any_name"> +``` + +### Advanced: How sqlgen Enforces Nullability + +The nullability rules are enforced at compile time using template metaprogramming (see `underlying_t.hpp`). This ensures that: +- You cannot accidentally assign a nullable result to a non-nullable field. +- All arguments to `coalesce` must have the same base type (e.g., all `int` or all `std::string`). +- The result type of any operation is always correct and safe to use in your result structs. + +--- + +## Example: Combining Operations + +```cpp +struct Children { + int id_plus_age; + int age_times_2; + int id_plus_2_minus_age; + int abs_age; + double exp_age; + double sqrt_age; + size_t length_first_name; + std::string full_name; + std::string first_name_lower; + std::string first_name_upper; + std::string first_name_replaced; +}; + +const auto get_children = select_from( + ("id"_c + "age"_c) | as<"id_plus_age">, + ("age"_c * 2) | as<"age_times_2">, + abs("age"_c * (-1)) | as<"abs_age">, + round(exp(cast("age"_c)), 2) | as<"exp_age">, + round(sqrt(cast("age"_c)), 2) | as<"sqrt_age">, + length(trim("first_name"_c)) | as<"length_first_name">, + concat("first_name"_c, " ", "last_name"_c) | as<"full_name">, + lower("first_name"_c) | as<"first_name_lower">, + upper("first_name"_c, " ") | as<"first_name_upper">, + replace("first_name"_c, "Bart", "Hugo") | as<"first_name_replaced"> +) | where("age"_c < 18) | to>; +``` + +This generates the following SQL: + +```sql +SELECT + ("id" + "age") AS "id_plus_age", + ("age" * 2) AS "age_times_2", + ABS(("age" * -1)) AS "abs_age", + ROUND(EXP(CAST("age" AS NUMERIC)), 2) AS "exp_age", + ROUND(SQRT(CAST("age" AS NUMERIC)), 2) AS "sqrt_age", + LENGTH(TRIM("first_name")) AS "length_first_name", + ("first_name" || ' ' || "last_name") AS "full_name", + LOWER("first_name") AS "first_name_lower", + UPPER("first_name") AS "first_name_upper", + REPLACE("first_name", 'Bart', 'Hugo') AS "first_name_replaced" +FROM "Person" +WHERE "age" < 18; +``` + +--- + +## Notes + +- All functions are type-safe and map to the appropriate SQL operations for the target database. +- You can chain and nest operations as needed. +- Use the `as<"alias">(...)` or `| as<"alias">` syntax to alias expressions for mapping to struct fields. + diff --git a/include/sqlgen.hpp b/include/sqlgen.hpp index dbb53e2..ed75e19 100644 --- a/include/sqlgen.hpp +++ b/include/sqlgen.hpp @@ -30,6 +30,7 @@ #include "sqlgen/insert.hpp" #include "sqlgen/is_connection.hpp" #include "sqlgen/limit.hpp" +#include "sqlgen/operations.hpp" #include "sqlgen/order_by.hpp" #include "sqlgen/patterns.hpp" #include "sqlgen/read.hpp" diff --git a/include/sqlgen/aggregations.hpp b/include/sqlgen/aggregations.hpp index 3427903..37e33f6 100644 --- a/include/sqlgen/aggregations.hpp +++ b/include/sqlgen/aggregations.hpp @@ -5,48 +5,61 @@ #include #include "col.hpp" -#include "transpilation/aggregations.hpp" +#include "transpilation/Aggregation.hpp" +#include "transpilation/AggregationOp.hpp" +#include "transpilation/to_transpilation_type.hpp" namespace sqlgen { -template -auto avg(const Col<_name>&) { - return transpilation::aggregations::Avg>{ - .val = transpilation::Col<_name>{}}; +template +auto avg(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Aggregation{ + .val = transpilation::to_transpilation_type(_t)}; } inline auto count() { - return transpilation::aggregations::Count{}; + return transpilation::Aggregation{}; } template auto count(const Col<_name>&) { - return transpilation::aggregations::Count>{ + return transpilation::Aggregation>{ .val = transpilation::Col<_name>{}}; } template auto count_distinct(const Col<_name>&) { - return transpilation::aggregations::Count>{ + return transpilation::Aggregation>{ .val = transpilation::Col<_name>{}, .distinct = true}; } -template -auto max(const Col<_name>&) { - return transpilation::aggregations::Max>{ - .val = transpilation::Col<_name>{}}; +template +auto max(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Aggregation{ + .val = transpilation::to_transpilation_type(_t)}; } -template -auto min(const Col<_name>&) { - return transpilation::aggregations::Min>{ - .val = transpilation::Col<_name>{}}; +template +auto min(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Aggregation{ + .val = transpilation::to_transpilation_type(_t)}; } -template -auto sum(const Col<_name>&) { - return transpilation::aggregations::Sum>{ - .val = transpilation::Col<_name>{}}; +template +auto sum(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Aggregation{ + .val = transpilation::to_transpilation_type(_t)}; } } // namespace sqlgen diff --git a/include/sqlgen/col.hpp b/include/sqlgen/col.hpp index e1a693e..5be2e4c 100644 --- a/include/sqlgen/col.hpp +++ b/include/sqlgen/col.hpp @@ -8,9 +8,12 @@ #include "transpilation/Col.hpp" #include "transpilation/Condition.hpp" #include "transpilation/Desc.hpp" +#include "transpilation/Operation.hpp" +#include "transpilation/Operator.hpp" #include "transpilation/Set.hpp" #include "transpilation/Value.hpp" #include "transpilation/conditions.hpp" +#include "transpilation/to_transpilation_type.hpp" namespace sqlgen { @@ -77,6 +80,103 @@ struct Col { return transpilation::Set, std::string>{.to = _to}; } + + template + friend auto operator==(const Col&, const T& _t) { + return transpilation::make_condition(transpilation::conditions::equal( + transpilation::Col<_name>{}, transpilation::to_transpilation_type(_t))); + } + + template + friend auto operator!=(const Col&, const T& _t) { + return transpilation::make_condition(transpilation::conditions::not_equal( + transpilation::Col<_name>{}, transpilation::to_transpilation_type(_t))); + } + + template + friend auto operator<(const Col&, const T& _t) { + return transpilation::make_condition(transpilation::conditions::lesser_than( + transpilation::Col<_name>{}, transpilation::to_transpilation_type(_t))); + } + + template + friend auto operator<=(const Col&, const T& _t) { + return transpilation::make_condition( + transpilation::conditions::lesser_equal( + transpilation::Col<_name>{}, + transpilation::to_transpilation_type(_t))); + } + + template + friend auto operator>(const Col&, const T& _t) { + return transpilation::make_condition( + transpilation::conditions::greater_than( + transpilation::Col<_name>{}, + transpilation::to_transpilation_type(_t))); + } + + template + friend auto operator>=(const Col&, const T& _t) { + return transpilation::make_condition( + transpilation::conditions::greater_equal( + transpilation::Col<_name>{}, + transpilation::to_transpilation_type(_t))); + } + + template + friend auto operator/(const Col&, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return transpilation::Operation, OtherType>{ + .operand1 = transpilation::Col<_name>{}, + .operand2 = transpilation::to_transpilation_type(_op2)}; + } + + template + friend auto operator-(const Col&, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return transpilation::Operation, OtherType>{ + .operand1 = transpilation::Col<_name>{}, + .operand2 = transpilation::to_transpilation_type(_op2)}; + } + + template + friend auto operator%(const Col&, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return transpilation::Operation, OtherType>{ + .operand1 = transpilation::Col<_name>{}, + .operand2 = transpilation::to_transpilation_type(_op2)}; + } + + template + friend auto operator*(const Col&, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return transpilation::Operation, OtherType>{ + .operand1 = transpilation::Col<_name>{}, + .operand2 = transpilation::to_transpilation_type(_op2)}; + } + + template + friend auto operator+(const Col&, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return transpilation::Operation, OtherType>{ + .operand1 = transpilation::Col<_name>{}, + .operand2 = transpilation::to_transpilation_type(_op2)}; + } }; template @@ -87,83 +187,18 @@ auto operator"" _c() { return Col<_name>{}; } -template -auto operator==(const Col<_name1>&, const Col<_name2>&) { - return transpilation::make_condition(transpilation::conditions::equal( - transpilation::Col<_name1>{}, transpilation::Col<_name2>{})); -} +namespace transpilation { -template -auto operator==(const Col<_name1>&, const T& _t) { - return transpilation::make_condition(transpilation::conditions::equal( - transpilation::Col<_name1>{}, transpilation::make_value(_t))); -} +template +struct ToTranspilationType> { + using Type = transpilation::Col<_name>; -template -auto operator!=(const Col<_name1>&, const Col<_name2>&) { - return transpilation::make_condition(transpilation::conditions::not_equal( - transpilation::Col<_name1>{}, transpilation::Col<_name2>{})); -} + Type operator()(const auto&) const noexcept { + return transpilation::Col<_name>{}; + } +}; -template -auto operator!=(const Col<_name1>&, const T& _t) { - return transpilation::make_condition(transpilation::conditions::not_equal( - transpilation::Col<_name1>{}, transpilation::make_value(_t))); -} - -template -auto operator<(const Col<_name1>&, const Col<_name2>&) { - return transpilation::make_condition(transpilation::conditions::lesser_than( - transpilation::Col<_name1>{}, transpilation::Col<_name2>{})); -} - -template -auto operator<(const Col<_name1>&, const T& _t) { - return transpilation::make_condition(transpilation::conditions::lesser_than( - transpilation::Col<_name1>{}, transpilation::make_value(_t))); -} - -template -auto operator<=(const Col<_name1>&, const Col<_name2>&) { - return transpilation::make_condition(transpilation::conditions::lesser_equal( - transpilation::Col<_name1>{}, transpilation::Col<_name2>{})); -} - -template -auto operator<=(const Col<_name1>&, const T& _t) { - return transpilation::make_condition(transpilation::conditions::lesser_equal( - transpilation::Col<_name1>{}, transpilation::make_value(_t))); -} - -template -auto operator>(const Col<_name1>&, const Col<_name2>&) { - return transpilation::make_condition(transpilation::conditions::greater_than( - transpilation::Col<_name1>{}, transpilation::Col<_name2>{})); -} - -template -auto operator>(const Col<_name1>&, const T& _t) { - return transpilation::make_condition(transpilation::conditions::greater_than( - transpilation::Col<_name1>{}, transpilation::make_value(_t))); -} - -template -auto operator>=(const Col<_name1>&, const Col<_name2>&) { - return transpilation::make_condition(transpilation::conditions::greater_equal( - transpilation::Col<_name1>{}, transpilation::Col<_name2>{})); -} - -template -auto operator>=(const Col<_name1>&, const T& _t) { - return transpilation::make_condition(transpilation::conditions::greater_equal( - transpilation::Col<_name1>{}, transpilation::make_value(_t))); -} +} // namespace transpilation } // namespace sqlgen diff --git a/include/sqlgen/dynamic/Aggregation.hpp b/include/sqlgen/dynamic/Aggregation.hpp index 3537fd8..e98c815 100644 --- a/include/sqlgen/dynamic/Aggregation.hpp +++ b/include/sqlgen/dynamic/Aggregation.hpp @@ -1,44 +1,11 @@ #ifndef SQLGEN_DYNAMIC_AGGREGATION_HPP_ #define SQLGEN_DYNAMIC_AGGREGATION_HPP_ -#include -#include -#include -#include - -#include "Column.hpp" -#include "ColumnOrValue.hpp" +#include "Operation.hpp" namespace sqlgen::dynamic { -struct Aggregation { - struct Avg { - ColumnOrValue val; - }; - - struct Count { - std::optional val; - bool distinct = false; - }; - - struct Max { - ColumnOrValue val; - }; - - struct Min { - ColumnOrValue val; - }; - - struct Sum { - ColumnOrValue val; - }; - - using ReflectionType = rfl::TaggedUnion<"what", Avg, Count, Max, Min, Sum>; - - const ReflectionType& reflection() const { return val; } - - ReflectionType val; -}; +using Aggregation = Operation::Aggregation; } // namespace sqlgen::dynamic diff --git a/include/sqlgen/dynamic/Condition.hpp b/include/sqlgen/dynamic/Condition.hpp index 9ffd2a9..75ac4d0 100644 --- a/include/sqlgen/dynamic/Condition.hpp +++ b/include/sqlgen/dynamic/Condition.hpp @@ -6,6 +6,7 @@ #include "../Ref.hpp" #include "Column.hpp" #include "ColumnOrValue.hpp" +#include "Operation.hpp" namespace sqlgen::dynamic { @@ -16,50 +17,54 @@ struct Condition { }; struct Equal { - Column op1; - ColumnOrValue op2; + Operation op1; + Operation op2; }; struct GreaterEqual { - Column op1; - ColumnOrValue op2; + Operation op1; + Operation op2; }; struct GreaterThan { - Column op1; - ColumnOrValue op2; + Operation op1; + Operation op2; }; struct IsNotNull { - Column op; + Operation op; }; struct IsNull { - Column op; + Operation op; }; struct LesserEqual { - Column op1; - ColumnOrValue op2; + Operation op1; + Operation op2; }; struct LesserThan { - Column op1; - ColumnOrValue op2; + Operation op1; + Operation op2; }; struct Like { - Column op; + Operation op; dynamic::Value pattern; }; + struct Not { + Ref cond; + }; + struct NotEqual { - Column op1; - ColumnOrValue op2; + Operation op1; + Operation op2; }; struct NotLike { - Column op; + Operation op; dynamic::Value pattern; }; @@ -70,7 +75,7 @@ struct Condition { using ReflectionType = rfl::TaggedUnion<"what", And, Equal, GreaterEqual, GreaterThan, IsNull, - IsNotNull, LesserEqual, LesserThan, Like, NotEqual, + IsNotNull, LesserEqual, LesserThan, Like, Not, NotEqual, NotLike, Or>; const ReflectionType& reflection() const { return val; } diff --git a/include/sqlgen/dynamic/Operation.hpp b/include/sqlgen/dynamic/Operation.hpp new file mode 100644 index 0000000..7360521 --- /dev/null +++ b/include/sqlgen/dynamic/Operation.hpp @@ -0,0 +1,175 @@ +#ifndef SQLGEN_DYNAMIC_OPERATION_HPP_ +#define SQLGEN_DYNAMIC_OPERATION_HPP_ + +#include +#include +#include +#include + +#include "../Ref.hpp" +#include "Column.hpp" +#include "Type.hpp" +#include "Value.hpp" + +namespace sqlgen::dynamic { + +struct Operation { + struct Abs { + Ref op1; + }; + + struct Aggregation { + struct Avg { + Ref val; + }; + + struct Count { + std::optional val; + bool distinct = false; + }; + + struct Max { + Ref val; + }; + + struct Min { + Ref val; + }; + + struct Sum { + Ref val; + }; + + using ReflectionType = rfl::TaggedUnion<"what", Avg, Count, Max, Min, Sum>; + + const ReflectionType& reflection() const { return val; } + + ReflectionType val; + }; + + struct Cast { + Ref op1; + Type target_type; + }; + + struct Ceil { + Ref op1; + }; + + struct Coalesce { + std::vector> ops; + }; + + struct Concat { + std::vector> ops; + }; + + struct Cos { + Ref op1; + }; + + struct Divides { + Ref op1; + Ref op2; + }; + + struct Exp { + Ref op1; + }; + + struct Floor { + Ref op1; + }; + + struct Length { + Ref op1; + }; + + struct Ln { + Ref op1; + }; + + struct Lower { + Ref op1; + }; + + struct LTrim { + Ref op1; + Ref op2; + }; + + struct Log2 { + Ref op1; + }; + + struct Minus { + Ref op1; + Ref op2; + }; + + struct Mod { + Ref op1; + Ref op2; + }; + + struct Multiplies { + Ref op1; + Ref op2; + }; + + struct Plus { + Ref op1; + Ref op2; + }; + + struct Replace { + Ref op1; + Ref op2; + Ref op3; + }; + + struct Round { + Ref op1; + Ref op2; + }; + + struct RTrim { + Ref op1; + Ref op2; + }; + + struct Sin { + Ref op1; + }; + + struct Sqrt { + Ref op1; + }; + + struct Tan { + Ref op1; + }; + + struct Trim { + Ref op1; + Ref op2; + }; + + struct Upper { + Ref op1; + }; + + using ReflectionType = + rfl::TaggedUnion<"what", Abs, Aggregation, Cast, Ceil, Column, Coalesce, + Concat, Cos, Divides, Exp, Floor, Length, Ln, Log2, + Lower, LTrim, Minus, Mod, Multiplies, Plus, Replace, + Round, RTrim, Sin, Sqrt, Tan, Trim, Upper, Value>; + + const ReflectionType& reflection() const { return val; } + + ReflectionType val; +}; + +} // namespace sqlgen::dynamic + +#endif diff --git a/include/sqlgen/dynamic/SelectFrom.hpp b/include/sqlgen/dynamic/SelectFrom.hpp index b9d25c3..6876a96 100644 --- a/include/sqlgen/dynamic/SelectFrom.hpp +++ b/include/sqlgen/dynamic/SelectFrom.hpp @@ -6,20 +6,18 @@ #include #include -#include "Aggregation.hpp" -#include "Column.hpp" #include "Condition.hpp" #include "GroupBy.hpp" #include "Limit.hpp" +#include "Operation.hpp" #include "OrderBy.hpp" #include "Table.hpp" -#include "Value.hpp" namespace sqlgen::dynamic { struct SelectFrom { struct Field { - rfl::TaggedUnion<"type", Aggregation, Column, Value> val; + Operation val; std::optional as; }; diff --git a/include/sqlgen/dynamic/Value.hpp b/include/sqlgen/dynamic/Value.hpp index 4e1d934..c6b3259 100644 --- a/include/sqlgen/dynamic/Value.hpp +++ b/include/sqlgen/dynamic/Value.hpp @@ -18,7 +18,11 @@ struct String { std::string val; }; -using Value = rfl::TaggedUnion<"type", Float, Integer, String>; +struct Value { + using ReflectionType = rfl::TaggedUnion<"type", Float, Integer, String>; + const auto& reflection() const { return val; } + ReflectionType val; +}; } // namespace sqlgen::dynamic diff --git a/include/sqlgen/operations.hpp b/include/sqlgen/operations.hpp new file mode 100644 index 0000000..ca8c01a --- /dev/null +++ b/include/sqlgen/operations.hpp @@ -0,0 +1,229 @@ +#ifndef SQLGEN_OPERATIONS_HPP_ +#define SQLGEN_OPERATIONS_HPP_ + +#include +#include + +#include "col.hpp" +#include "transpilation/Operation.hpp" +#include "transpilation/Operator.hpp" +#include "transpilation/to_transpilation_type.hpp" + +namespace sqlgen { + +template +auto abs(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto cast(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation< + transpilation::Operator::cast, Type, + transpilation::TypeHolder>>{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto ceil(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto coalesce(const Ts&... _ts) { + static_assert(sizeof...(_ts) > 1, + "coalesce(...) must have at least two inputs."); + using Type = rfl::Tuple>::Type...>; + return transpilation::Operation{ + .operand1 = Type(transpilation::to_transpilation_type(_ts)...)}; +} + +template +auto concat(const Ts&... _ts) { + static_assert(sizeof...(_ts) > 0, + "concat(...) must have at least one input."); + using Type = rfl::Tuple>::Type...>; + return transpilation::Operation{ + .operand1 = Type(transpilation::to_transpilation_type(_ts)...)}; +} + +template +auto cos(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto exp(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto floor(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto length(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto ln(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto log2(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto lower(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto ltrim(const T& _t, const U& _u) { + using Type1 = + typename transpilation::ToTranspilationType>::Type; + using Type2 = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t), + .operand2 = transpilation::to_transpilation_type(_u)}; +} + +template +auto ltrim(const T& _t) { + return ltrim(_t, std::string(" ")); +} + +template +auto replace(const StringType& _str, const FromType& _from, const ToType& _to) { + using Type1 = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + using Type2 = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + using Type3 = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_str), + .operand2 = transpilation::to_transpilation_type(_from), + .operand3 = transpilation::to_transpilation_type(_to)}; +} + +template +auto round(const T& _t, const U& _u) { + using Type1 = + typename transpilation::ToTranspilationType>::Type; + using Type2 = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t), + .operand2 = transpilation::to_transpilation_type(_u)}; +} + +template +auto round(const T& _t) { + return round(_t, 0); +} + +template +auto rtrim(const T& _t, const U& _u) { + using Type1 = + typename transpilation::ToTranspilationType>::Type; + using Type2 = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t), + .operand2 = transpilation::to_transpilation_type(_u)}; +} + +template +auto rtrim(const T& _t) { + return rtrim(_t, std::string(" ")); +} + +template +auto sin(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto sqrt(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto tan(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +template +auto trim(const T& _t, const U& _u) { + using Type1 = + typename transpilation::ToTranspilationType>::Type; + using Type2 = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t), + .operand2 = transpilation::to_transpilation_type(_u)}; +} + +template +auto trim(const T& _t) { + return trim(_t, std::string(" ")); +} + +template +auto upper(const T& _t) { + using Type = + typename transpilation::ToTranspilationType>::Type; + return transpilation::Operation{ + .operand1 = transpilation::to_transpilation_type(_t)}; +} + +} // namespace sqlgen + +#endif diff --git a/include/sqlgen/transpilation/Aggregation.hpp b/include/sqlgen/transpilation/Aggregation.hpp new file mode 100644 index 0000000..058eda3 --- /dev/null +++ b/include/sqlgen/transpilation/Aggregation.hpp @@ -0,0 +1,86 @@ +#ifndef SQLGEN_TRANSPILATION_AGGREGATION_HPP_ +#define SQLGEN_TRANSPILATION_AGGREGATION_HPP_ + +#include + +#include "AggregationOp.hpp" +#include "As.hpp" +#include "Operation.hpp" +#include "Operator.hpp" +#include "to_transpilation_type.hpp" + +namespace sqlgen::transpilation { + +/// To be used when we want to count everything. +struct All {}; + +template +struct Aggregation { + static constexpr auto agg = _agg; + using ValueType = _ValueType; + + template + auto as() const noexcept { + using T = std::remove_cvref_t; + return transpilation::As{.val = *this}; + } + + ValueType val; + bool distinct = false; + + template + friend auto operator/(const Aggregation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } + + template + friend auto operator-(const Aggregation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } + + template + friend auto operator%(const Aggregation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } + + template + friend auto operator*(const Aggregation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } + + template + friend auto operator+(const Aggregation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } +}; + +template +struct ToTranspilationType> { + using Type = Aggregation<_agg, _ValueType>; + + Type operator()(const Type& _val) const noexcept { return _val; } +}; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/AggregationOp.hpp b/include/sqlgen/transpilation/AggregationOp.hpp new file mode 100644 index 0000000..f4edb24 --- /dev/null +++ b/include/sqlgen/transpilation/AggregationOp.hpp @@ -0,0 +1,10 @@ +#ifndef SQLGEN_TRANSPILATION_AGGREGATIONOP_HPP_ +#define SQLGEN_TRANSPILATION_AGGREGATIONOP_HPP_ + +namespace sqlgen::transpilation { + +enum class AggregationOp { avg, count, max, min, sum }; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/Condition.hpp b/include/sqlgen/transpilation/Condition.hpp index 1b86417..a8db48e 100644 --- a/include/sqlgen/transpilation/Condition.hpp +++ b/include/sqlgen/transpilation/Condition.hpp @@ -11,6 +11,22 @@ template struct Condition { using ConditionType = _ConditionType; ConditionType condition; + + auto operator!() { + return make_condition(conditions::Not<_ConditionType>{.cond = condition}); + } + + template + friend auto operator&&(const Condition& _cond1, const Condition& _cond2) { + return make_condition(conditions::And<_ConditionType, C2>{ + .cond1 = _cond1.condition, .cond2 = _cond2.condition}); + } + + template + friend auto operator||(const Condition& _cond1, const Condition& _cond2) { + return make_condition(conditions::Or<_ConditionType, C2>{ + .cond1 = _cond1.condition, .cond2 = _cond2.condition}); + } }; template @@ -18,18 +34,6 @@ auto make_condition(T&& _t) { return Condition>{.condition = _t}; } -template -auto operator&&(const Condition& _cond1, const Condition& _cond2) { - return make_condition(conditions::And{.cond1 = _cond1.condition, - .cond2 = _cond2.condition}); -} - -template -auto operator||(const Condition& _cond1, const Condition& _cond2) { - return make_condition(conditions::Or{.cond1 = _cond1.condition, - .cond2 = _cond2.condition}); -} - } // namespace sqlgen::transpilation #endif diff --git a/include/sqlgen/transpilation/Operation.hpp b/include/sqlgen/transpilation/Operation.hpp new file mode 100644 index 0000000..887b9e9 --- /dev/null +++ b/include/sqlgen/transpilation/Operation.hpp @@ -0,0 +1,153 @@ +#ifndef SQLGEN_TRANSPILATION_OPERATION_HPP_ +#define SQLGEN_TRANSPILATION_OPERATION_HPP_ + +#include +#include + +#include "../Result.hpp" +#include "As.hpp" +#include "Condition.hpp" +#include "Operator.hpp" +#include "conditions.hpp" +#include "to_transpilation_type.hpp" + +namespace sqlgen::transpilation { + +/// Simple abstraction to be used for the cast operation. +template +struct TypeHolder {}; + +template +struct Operation { + static constexpr Operator op = _op; + + using Operand1Type = _Operand1Type; + using Operand2Type = _Operand2Type; + using Operand3Type = _Operand3Type; + + Operand1Type operand1; + Operand2Type operand2; + Operand3Type operand3; + + template + auto as() const noexcept { + using T = std::remove_cvref_t; + return transpilation::As{.val = *this}; + } + + /// Returns an IS NULL condition. + auto is_null() const noexcept { + return make_condition(conditions::is_null(*this)); + } + + /// Returns a IS NOT NULL condition. + auto is_not_null() const noexcept { + return make_condition(conditions::is_not_null(*this)); + } + + /// Returns a LIKE condition. + auto like(const std::string& _pattern) const noexcept { + return make_condition(conditions::like(*this, _pattern)); + } + + /// Returns a NOT LIKE condition. + auto not_like(const std::string& _pattern) const noexcept { + return make_condition(conditions::not_like(*this, _pattern)); + } + + template + friend auto operator==(const Operation& _o, const T& _t) { + return make_condition(conditions::equal(_o, to_transpilation_type(_t))); + } + + template + friend auto operator!=(const Operation& _o, const T& _t) { + return make_condition(conditions::not_equal(_o, to_transpilation_type(_t))); + } + + template + friend auto operator<(const Operation& _o, const T& _t) { + return make_condition( + conditions::lesser_than(_o, to_transpilation_type(_t))); + } + + template + friend auto operator<=(const Operation& _o, const T& _t) { + return make_condition( + conditions::lesser_equal(_o, to_transpilation_type(_t))); + } + + template + friend auto operator>(const Operation& _o, const T& _t) { + return make_condition( + conditions::greater_than(_o, to_transpilation_type(_t))); + } + + template + friend auto operator>=(const Operation& _o, const T& _t) { + return make_condition( + conditions::greater_equal(_o, to_transpilation_type(_t))); + } + + template + friend auto operator/(const Operation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation, OtherType>{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } + + template + friend auto operator-(const Operation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation, OtherType>{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } + + template + friend auto operator%(const Operation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation, OtherType>{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } + + template + friend auto operator*(const Operation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation, OtherType>{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } + + template + friend auto operator+(const Operation& _op1, const T& _op2) noexcept { + using OtherType = typename transpilation::ToTranspilationType< + std::remove_cvref_t>::Type; + + return Operation, OtherType>{ + .operand1 = _op1, .operand2 = to_transpilation_type(_op2)}; + } +}; + +template +struct ToTranspilationType> { + using Type = Operation<_op, _Operand1Type, _Operand2Type>; + + Type operator()(const Type& _val) const noexcept { return _val; } +}; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/Operator.hpp b/include/sqlgen/transpilation/Operator.hpp new file mode 100644 index 0000000..b6a037e --- /dev/null +++ b/include/sqlgen/transpilation/Operator.hpp @@ -0,0 +1,37 @@ +#ifndef SQLGEN_TRANSPILATION_OPERATOR_HPP_ +#define SQLGEN_TRANSPILATION_OPERATOR_HPP_ + +namespace sqlgen::transpilation { + +enum class Operator { + abs, + cast, + ceil, + coalesce, + concat, + cos, + divides, + exp, + floor, + length, + ln, + log2, + lower, + ltrim, + minus, + mod, + multiplies, + plus, + replace, + round, + rtrim, + sin, + sqrt, + tan, + trim, + upper +}; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/OperatorCategory.hpp b/include/sqlgen/transpilation/OperatorCategory.hpp new file mode 100644 index 0000000..3092d82 --- /dev/null +++ b/include/sqlgen/transpilation/OperatorCategory.hpp @@ -0,0 +1,10 @@ +#ifndef SQLGEN_TRANSPILATION_OPERATORCATEGORY_HPP_ +#define SQLGEN_TRANSPILATION_OPERATORCATEGORY_HPP_ + +namespace sqlgen::transpilation { + +enum class OperatorCategory { numerical, string, other }; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/aggregations.hpp b/include/sqlgen/transpilation/aggregations.hpp deleted file mode 100644 index c40e180..0000000 --- a/include/sqlgen/transpilation/aggregations.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef SQLGEN_TRANSPILATION_AGGREGATIONS_HPP_ -#define SQLGEN_TRANSPILATION_AGGREGATIONS_HPP_ - -#include - -#include "As.hpp" - -namespace sqlgen::transpilation::aggregations { - -/// To be used when we want to count everything. -struct All {}; - -template -struct Avg { - using ValueType = _ValueType; - - template - auto as() const noexcept { - using T = std::remove_cvref_t; - return transpilation::As{.val = *this}; - } - - ValueType val; -}; - -template -struct Count { - using ValueType = _ValueType; - - template - auto as() const noexcept { - using T = std::remove_cvref_t; - return transpilation::As{.val = *this}; - } - - ValueType val; - bool distinct = false; -}; - -template -struct Max { - using ValueType = _ValueType; - - template - auto as() const noexcept { - using T = std::remove_cvref_t; - return transpilation::As{.val = *this}; - } - - ValueType val; -}; - -template -struct Min { - using ValueType = _ValueType; - - template - auto as() const noexcept { - using T = std::remove_cvref_t; - return transpilation::As{.val = *this}; - } - - ValueType val; -}; - -template -struct Sum { - using ValueType = _ValueType; - - template - auto as() const noexcept { - using T = std::remove_cvref_t; - return transpilation::As{.val = *this}; - } - - ValueType val; -}; - -} // namespace sqlgen::transpilation::aggregations - -#endif diff --git a/include/sqlgen/transpilation/check_aggregations.hpp b/include/sqlgen/transpilation/check_aggregations.hpp index 85b7498..05c7086 100644 --- a/include/sqlgen/transpilation/check_aggregations.hpp +++ b/include/sqlgen/transpilation/check_aggregations.hpp @@ -49,7 +49,8 @@ struct CheckAggregation, static constexpr bool value = (true && ... && (MakeField::is_aggregation || - !MakeField::is_column || + (!MakeField::is_column && + !MakeField::is_operation) || included_in_group_by)); static_assert(value, diff --git a/include/sqlgen/transpilation/conditions.hpp b/include/sqlgen/transpilation/conditions.hpp index ac3af5a..f9eb438 100644 --- a/include/sqlgen/transpilation/conditions.hpp +++ b/include/sqlgen/transpilation/conditions.hpp @@ -126,6 +126,13 @@ struct NotEqual { OpType2 op2; }; +template +struct Not { + using ResultType = bool; + + CondType cond; +}; + template auto not_equal(const OpType1& _op1, const OpType2& _op2) { return NotEqual, std::remove_cvref_t>{ diff --git a/include/sqlgen/transpilation/dynamic_aggregation_t.hpp b/include/sqlgen/transpilation/dynamic_aggregation_t.hpp new file mode 100644 index 0000000..bc8177f --- /dev/null +++ b/include/sqlgen/transpilation/dynamic_aggregation_t.hpp @@ -0,0 +1,42 @@ +#ifndef SQLGEN_TRANSPILATION_DYNAMICAGGREGATIONT_HPP_ +#define SQLGEN_TRANSPILATION_DYNAMICAGGREGATIONT_HPP_ + +#include "../dynamic/Aggregation.hpp" +#include "AggregationOp.hpp" + +namespace sqlgen::transpilation { + +template +struct DynamicAggregation; + +template <> +struct DynamicAggregation { + using Type = dynamic::Aggregation::Avg; +}; + +template <> +struct DynamicAggregation { + using Type = dynamic::Aggregation::Count; +}; + +template <> +struct DynamicAggregation { + using Type = dynamic::Aggregation::Max; +}; + +template <> +struct DynamicAggregation { + using Type = dynamic::Aggregation::Min; +}; + +template <> +struct DynamicAggregation { + using Type = dynamic::Aggregation::Sum; +}; + +template +using dynamic_aggregation_t = typename DynamicAggregation::Type; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/dynamic_operator_t.hpp b/include/sqlgen/transpilation/dynamic_operator_t.hpp new file mode 100644 index 0000000..95c696e --- /dev/null +++ b/include/sqlgen/transpilation/dynamic_operator_t.hpp @@ -0,0 +1,208 @@ +#ifndef SQLGEN_TRANSPILATION_DYNAMICOPERATORT_HPP_ +#define SQLGEN_TRANSPILATION_DYNAMICOPERATORT_HPP_ + +#include + +#include "../dynamic/Operation.hpp" +#include "Operator.hpp" +#include "OperatorCategory.hpp" + +namespace sqlgen::transpilation { + +template +struct DynamicOperator; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Abs; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::other; + using Type = dynamic::Operation::Cast; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Ceil; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = std::numeric_limits::max(); + static constexpr auto category = OperatorCategory::other; + using Type = dynamic::Operation::Coalesce; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = std::numeric_limits::max(); + static constexpr auto category = OperatorCategory::string; + using Type = dynamic::Operation::Concat; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Cos; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 2; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Divides; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Exp; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Floor; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::string; + using Type = dynamic::Operation::Length; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Ln; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Log2; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::string; + using Type = dynamic::Operation::Lower; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 2; + static constexpr auto category = OperatorCategory::string; + using Type = dynamic::Operation::LTrim; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 2; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Minus; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 2; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Mod; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 2; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Multiplies; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 2; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Plus; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 3; + static constexpr auto category = OperatorCategory::other; + using Type = dynamic::Operation::Replace; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 2; + static constexpr auto category = OperatorCategory::other; + using Type = dynamic::Operation::Round; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 2; + static constexpr auto category = OperatorCategory::string; + using Type = dynamic::Operation::RTrim; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Sin; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Sqrt; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::numerical; + using Type = dynamic::Operation::Tan; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 2; + static constexpr auto category = OperatorCategory::string; + using Type = dynamic::Operation::Trim; +}; + +template <> +struct DynamicOperator { + static constexpr size_t num_operands = 1; + static constexpr auto category = OperatorCategory::string; + using Type = dynamic::Operation::Upper; +}; + +template +using dynamic_operator_t = typename DynamicOperator::Type; + +template +inline constexpr size_t num_operands_v = DynamicOperator::num_operands; + +template +inline constexpr auto operator_category_v = DynamicOperator::category; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/flatten_fields_t.hpp b/include/sqlgen/transpilation/flatten_fields_t.hpp new file mode 100644 index 0000000..441e3ba --- /dev/null +++ b/include/sqlgen/transpilation/flatten_fields_t.hpp @@ -0,0 +1,64 @@ +#ifndef SQLGEN_TRANSPILATION_FLATTEN_FIELDS_T_HPP_ +#define SQLGEN_TRANSPILATION_FLATTEN_FIELDS_T_HPP_ + +#include +#include + +#include "As.hpp" +#include "make_field.hpp" +#include "remove_as_t.hpp" + +namespace sqlgen::transpilation { + +template +struct TupleWrapper; + +template +struct TupleWrapper> { + using Type = rfl::Tuple; + + template + friend consteval auto operator+( + const TupleWrapper&, + const TupleWrapper>&) { + return TupleWrapper>{}; + } +}; + +template +struct FlattenFields; + +template +struct ExtractFields; + +template + requires(!MakeField>::is_operation) +struct ExtractFields { + using Type = rfl::Tuple>; +}; + +template + requires(MakeField>::is_operation) +struct ExtractFields { + using Type = typename FlattenFields< + StructType, + typename MakeField>::Operands>::Type; +}; + +template +struct FlattenFields> { + static constexpr auto wrapper = + (TupleWrapper>{} + ... + + TupleWrapper::Type>{}); + + using Type = typename decltype(wrapper)::Type; +}; + +template +using flatten_fields_t = + typename FlattenFields, + std::remove_cvref_t>::Type; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/make_field.hpp b/include/sqlgen/transpilation/make_field.hpp index 5326cc3..5ebeb93 100644 --- a/include/sqlgen/transpilation/make_field.hpp +++ b/include/sqlgen/transpilation/make_field.hpp @@ -7,11 +7,18 @@ #include "../Literal.hpp" #include "../Result.hpp" #include "../dynamic/SelectFrom.hpp" +#include "../parsing/Parser.hpp" +#include "Aggregation.hpp" +#include "AggregationOp.hpp" #include "As.hpp" #include "Col.hpp" +#include "Operation.hpp" +#include "Operator.hpp" +#include "OperatorCategory.hpp" #include "Value.hpp" -#include "aggregations.hpp" #include "all_columns_exist.hpp" +#include "dynamic_aggregation_t.hpp" +#include "dynamic_operator_t.hpp" #include "remove_nullable_t.hpp" #include "to_value.hpp" #include "underlying_t.hpp" @@ -21,16 +28,18 @@ namespace sqlgen::transpilation { template struct MakeField; -template +template struct MakeField { static constexpr bool is_aggregation = false; static constexpr bool is_column = false; + static constexpr bool is_operation = false; using Name = Nothing; - using Type = ValueType; + using Type = std::remove_cvref_t; dynamic::SelectFrom::Field operator()(const auto& _val) const { - return dynamic::SelectFrom::Field{.val = to_value(_val)}; + return dynamic::SelectFrom::Field{ + dynamic::Operation{.val = to_value(_val)}}; } }; @@ -41,13 +50,29 @@ struct MakeField> { static constexpr bool is_aggregation = false; static constexpr bool is_column = true; + static constexpr bool is_operation = false; using Name = Literal<_name>; using Type = rfl::field_type_t<_name, StructType>; dynamic::SelectFrom::Field operator()(const auto&) const { - return dynamic::SelectFrom::Field{.val = - dynamic::Column{.name = _name.str()}}; + return dynamic::SelectFrom::Field{ + dynamic::Operation{.val = dynamic::Column{.name = _name.str()}}}; + } +}; + +template +struct MakeField> { + static constexpr bool is_aggregation = false; + static constexpr bool is_column = false; + static constexpr bool is_operation = false; + + using Name = Nothing; + using Type = std::remove_cvref_t; + + dynamic::SelectFrom::Field operator()(const auto& _val) const { + return dynamic::SelectFrom::Field{ + dynamic::Operation{.val = to_value(_val.val)}}; } }; @@ -57,6 +82,8 @@ struct MakeField> { static constexpr bool is_aggregation = MakeField::is_aggregation; static constexpr bool is_column = MakeField::is_column; + static constexpr bool is_operation = + MakeField::is_operation; using Name = Literal<_new_name>; using Type = @@ -64,132 +91,286 @@ struct MakeField> { dynamic::SelectFrom::Field operator()(const auto& _as) const { return dynamic::SelectFrom::Field{ - .val = MakeField>{}(_as.val) - .val, + .val = + dynamic::Operation{ + .val = MakeField>{}( + _as.val) + .val.val}, .as = _new_name.str()}; } }; -template -struct MakeField>> { - static_assert(all_columns_exist>(), - "A column required in the AVG aggregation does not exist."); - - static_assert( - std::is_integral_v< - remove_nullable_t>>> || - std::is_floating_point_v< - remove_nullable_t>>>, - "Values inside the AVG aggregation must be numerical."); +template +struct MakeField> { + static_assert(std::is_integral_v< + remove_nullable_t>> || + std::is_floating_point_v< + remove_nullable_t>>, + "Values inside the aggregation must be numerical."); static constexpr bool is_aggregation = true; static constexpr bool is_column = true; + static constexpr bool is_operation = false; - using Name = Literal<_name>; - using Type = rfl::field_type_t<_name, StructType>; + using Name = Nothing; + using Type = + typename MakeField>::Type; - dynamic::SelectFrom::Field operator()(const auto&) const { - return dynamic::SelectFrom::Field{ - .val = dynamic::Aggregation{dynamic::Aggregation::Avg{ - .val = dynamic::Column{.name = _name.str()}}}}; + dynamic::SelectFrom::Field operator()(const auto& _val) const { + using DynamicAggregationType = dynamic_aggregation_t<_agg>; + return dynamic::SelectFrom::Field{dynamic::Operation{ + .val = dynamic::Aggregation{DynamicAggregationType{ + .val = Ref::make( + MakeField>{}( + _val.val) + .val)}}}}; } }; template -struct MakeField>> { +struct MakeField>> { static_assert(all_columns_exist>(), "A column required in the COUNT or COUNT_DISTINCT aggregation " "does not exist."); static constexpr bool is_aggregation = true; static constexpr bool is_column = true; + static constexpr bool is_operation = false; using Name = Literal<_name>; using Type = size_t; dynamic::SelectFrom::Field operator()(const auto& _agg) const { - return dynamic::SelectFrom::Field{ + return dynamic::SelectFrom::Field{dynamic::Operation{ .val = dynamic::Aggregation{dynamic::Aggregation::Count{ .val = dynamic::Column{.name = _name.str()}, - .distinct = _agg.distinct}}, - }; + .distinct = _agg.distinct}}}}; } }; template -struct MakeField> { +struct MakeField> { static constexpr bool is_aggregation = true; static constexpr bool is_column = true; + static constexpr bool is_operation = false; using Name = Nothing; using Type = size_t; dynamic::SelectFrom::Field operator()(const auto&) const { - return dynamic::SelectFrom::Field{ + return dynamic::SelectFrom::Field{dynamic::Operation{ .val = dynamic::Aggregation{ dynamic::Aggregation::Count{.val = std::nullopt, .distinct = false}, - }}; + }}}; } }; -template -struct MakeField>> { - static_assert(all_columns_exist>(), - "A column required in the MAX aggregation does not exist."); +template +struct MakeField>> { + static constexpr bool is_aggregation = false; + static constexpr bool is_column = false; + static constexpr bool is_operation = true; - static constexpr bool is_aggregation = true; - static constexpr bool is_column = true; + using Name = Nothing; + using Type = std::remove_cvref_t; + using Operands = rfl::Tuple; - using Name = Literal<_name>; - using Type = rfl::field_type_t<_name, StructType>; - - dynamic::SelectFrom::Field operator()(const auto&) const { + dynamic::SelectFrom::Field operator()(const auto& _o) const { return dynamic::SelectFrom::Field{ - .val = dynamic::Aggregation{dynamic::Aggregation::Max{ - .val = dynamic::Column{.name = _name.str()}}}}; + dynamic::Operation{dynamic::Operation::Cast{ + .op1 = Ref::make( + MakeField>{}( + _o.operand1) + .val), + .target_type = + parsing::Parser>::to_type()}}}; } }; -template -struct MakeField>> { - static_assert(all_columns_exist>(), - "A column required in MIN aggregation does not exist."); +template + requires((_op == Operator::coalesce) || (_op == Operator::concat)) +struct MakeField>> { + static constexpr bool is_aggregation = false; + static constexpr bool is_column = false; + static constexpr bool is_operation = true; - static constexpr bool is_aggregation = true; - static constexpr bool is_column = true; + using Name = Nothing; + using Type = + underlying_t>>; + using Operands = rfl::Tuple; - using Name = Literal<_name>; - using Type = rfl::field_type_t<_name, StructType>; - - dynamic::SelectFrom::Field operator()(const auto&) const { - return dynamic::SelectFrom::Field{ - .val = dynamic::Aggregation{dynamic::Aggregation::Min{ - .val = dynamic::Column{.name = _name.str()}}}}; + dynamic::SelectFrom::Field operator()(const auto& _o) const { + using DynamicOperatorType = dynamic_operator_t<_op>; + return dynamic::SelectFrom::Field{dynamic::Operation{DynamicOperatorType{ + .ops = rfl::apply( + [](const auto&... _ops) { + return std::vector>( + {Ref::make( + MakeField>{}(_ops) + .val)...}); + }, + _o.operand1)}}}; } }; -template -struct MakeField>> { - static_assert(all_columns_exist>(), - "A column required in SUM aggregation does not exist."); +template +struct MakeField> { + static constexpr bool is_aggregation = false; + static constexpr bool is_column = false; + static constexpr bool is_operation = true; - static_assert( - std::is_integral_v< - remove_nullable_t>>> || - std::is_floating_point_v< - remove_nullable_t>>>, - "Values inside the SUM aggregation must be numerical."); + using Name = Nothing; + using Type = + underlying_t>; + using Operands = rfl::Tuple; - static constexpr bool is_aggregation = true; - static constexpr bool is_column = true; - - using Name = Literal<_name>; - using Type = rfl::field_type_t<_name, StructType>; - - dynamic::SelectFrom::Field operator()(const auto&) const { + dynamic::SelectFrom::Field operator()(const auto& _o) const { return dynamic::SelectFrom::Field{ - .val = dynamic::Aggregation{dynamic::Aggregation::Sum{ - .val = dynamic::Column{.name = _name.str()}}}}; + dynamic::Operation{dynamic::Operation::Replace{ + .op1 = Ref::make( + MakeField>{}( + _o.operand1) + .val), + .op2 = Ref::make( + MakeField>{}( + _o.operand2) + .val), + .op3 = Ref::make( + MakeField>{}( + _o.operand3) + .val)}}}; + } +}; + +template +struct MakeField> { + static constexpr bool is_aggregation = false; + static constexpr bool is_column = false; + static constexpr bool is_operation = true; + + using Name = Nothing; + using Type = + underlying_t>; + using Operands = rfl::Tuple; + + dynamic::SelectFrom::Field operator()(const auto& _o) const { + return dynamic::SelectFrom::Field{ + dynamic::Operation{dynamic::Operation::Round{ + .op1 = Ref::make( + MakeField>{}( + _o.operand1) + .val), + .op2 = Ref::make( + MakeField>{}( + _o.operand2) + .val)}}}; + } +}; + +template + requires((num_operands_v<_op>) == 1 && + (operator_category_v<_op>) == OperatorCategory::string) +struct MakeField> { + static constexpr bool is_aggregation = false; + static constexpr bool is_column = false; + static constexpr bool is_operation = true; + + using Name = Nothing; + using Type = underlying_t>; + using Operands = rfl::Tuple; + + dynamic::SelectFrom::Field operator()(const auto& _o) const { + using DynamicOperatorType = dynamic_operator_t<_op>; + return dynamic::SelectFrom::Field{dynamic::Operation{DynamicOperatorType{ + .op1 = Ref::make( + MakeField>{}( + _o.operand1) + .val)}}}; + } +}; + +template + requires((num_operands_v<_op>) == 2 && + (operator_category_v<_op>) == OperatorCategory::string) +struct MakeField> { + static constexpr bool is_aggregation = false; + static constexpr bool is_column = false; + static constexpr bool is_operation = true; + + using Name = Nothing; + using Type = + underlying_t>; + using Operands = rfl::Tuple; + + dynamic::SelectFrom::Field operator()(const auto& _o) const { + using DynamicOperatorType = dynamic_operator_t<_op>; + return dynamic::SelectFrom::Field{dynamic::Operation{DynamicOperatorType{ + .op1 = Ref::make( + MakeField>{}( + _o.operand1) + .val), + .op2 = Ref::make( + MakeField>{}( + _o.operand2) + .val)}}}; + } +}; + +template + requires((num_operands_v<_op>) == 1 && + (operator_category_v<_op>) == OperatorCategory::numerical) +struct MakeField> { + static constexpr bool is_aggregation = false; + static constexpr bool is_column = false; + static constexpr bool is_operation = true; + + using Name = Nothing; + using Type = underlying_t>; + using Operands = rfl::Tuple; + + dynamic::SelectFrom::Field operator()(const auto& _o) const { + using DynamicOperatorType = dynamic_operator_t<_op>; + return dynamic::SelectFrom::Field{dynamic::Operation{DynamicOperatorType{ + .op1 = Ref::make( + MakeField>{}( + _o.operand1) + .val)}}}; + } +}; + +template + requires((num_operands_v<_op>) == 2 && + (operator_category_v<_op>) == OperatorCategory::numerical) +struct MakeField> { + static constexpr bool is_aggregation = false; + static constexpr bool is_column = false; + static constexpr bool is_operation = true; + + using Name = Nothing; + using Type = + underlying_t>; + using Operands = rfl::Tuple; + + dynamic::SelectFrom::Field operator()(const auto& _o) const { + using DynamicOperatorType = dynamic_operator_t<_op>; + return dynamic::SelectFrom::Field{dynamic::Operation{DynamicOperatorType{ + .op1 = Ref::make( + MakeField>{}( + _o.operand1) + .val), + .op2 = Ref::make( + MakeField>{}( + _o.operand2) + .val)}}}; } }; diff --git a/include/sqlgen/transpilation/to_condition.hpp b/include/sqlgen/transpilation/to_condition.hpp index c405a70..113b77c 100644 --- a/include/sqlgen/transpilation/to_condition.hpp +++ b/include/sqlgen/transpilation/to_condition.hpp @@ -12,7 +12,8 @@ #include "Condition.hpp" #include "all_columns_exist.hpp" #include "conditions.hpp" -#include "to_value.hpp" +#include "make_field.hpp" +#include "to_transpilation_type.hpp" #include "underlying_t.hpp" namespace sqlgen::transpilation { @@ -40,245 +41,135 @@ struct ToCondition> { } }; -template -struct ToCondition, Col<_name2>>> { - static_assert(all_columns_exist, Col<_name2>>(), - "All columns must exist."); - static_assert(std::equality_comparable_with>, - underlying_t>>, +template +struct ToCondition> { + static_assert(std::equality_comparable_with, + underlying_t>, "Must be equality comparable."); dynamic::Condition operator()(const auto& _cond) const { - return dynamic::Condition{.val = dynamic::Condition::Equal{ - .op1 = dynamic::Column{.name = _name1.str()}, - .op2 = dynamic::Column{.name = _name2.str()}, - }}; + return dynamic::Condition{ + .val = dynamic::Condition::Equal{.op1 = make_field(_cond.op1).val, + .op2 = make_field(_cond.op2).val}}; } }; -template -struct ToCondition, Value>> { - static_assert(all_columns_exist>(), "All columns must exist."); - static_assert(std::equality_comparable_with>, - underlying_t>>, - "Must be equality comparable."); - - dynamic::Condition operator()(const auto& _cond) const { - return dynamic::Condition{.val = dynamic::Condition::Equal{ - .op1 = dynamic::Column{.name = _name.str()}, - .op2 = to_value(_cond.op2.val), - }}; - } -}; - -template -struct ToCondition, Col<_name2>>> { - static_assert(all_columns_exist, Col<_name2>>(), - "All columns must exist."); - static_assert(std::totally_ordered_with>, - underlying_t>>, +template +struct ToCondition> { + static_assert(std::totally_ordered_with, + underlying_t>, "Must be totally ordered."); dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::GreaterEqual{ - .op1 = dynamic::Column{.name = _name1.str()}, - .op2 = dynamic::Column{.name = _name2.str()}, - }}; + .op1 = make_field(_cond.op1).val, + .op2 = make_field(_cond.op2).val}}; } }; -template -struct ToCondition, Value>> { - static_assert(all_columns_exist>(), "All columns must exist."); - static_assert(std::totally_ordered_with>, - underlying_t>>, - "Must be totally ordered."); - - dynamic::Condition operator()(const auto& _cond) const { - return dynamic::Condition{.val = dynamic::Condition::GreaterEqual{ - .op1 = dynamic::Column{.name = _name.str()}, - .op2 = to_value(_cond.op2.val), - }}; - } -}; - -template -struct ToCondition, Col<_name2>>> { - static_assert(all_columns_exist, Col<_name2>>(), - "All columns must exist."); - static_assert(std::totally_ordered_with>, - underlying_t>>, +template +struct ToCondition> { + static_assert(std::totally_ordered_with, + underlying_t>, "Must be totally ordered."); dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::GreaterThan{ - .op1 = dynamic::Column{.name = _name1.str()}, - .op2 = dynamic::Column{.name = _name2.str()}, - }}; + .op1 = make_field(_cond.op1).val, + .op2 = make_field(_cond.op2).val}}; } }; -template -struct ToCondition, Value>> { - static_assert(all_columns_exist>(), "All columns must exist."); - static_assert(std::totally_ordered_with>, - underlying_t>>, - "Must be totally ordered."); - - dynamic::Condition operator()(const auto& _cond) const { - return dynamic::Condition{.val = dynamic::Condition::GreaterThan{ - .op1 = dynamic::Column{.name = _name.str()}, - .op2 = to_value(_cond.op2.val), - }}; - } -}; - -template -struct ToCondition, Col<_name2>>> { - static_assert(all_columns_exist, Col<_name2>>(), - "All columns must exist."); - static_assert(std::totally_ordered_with>, - underlying_t>>, +template +struct ToCondition> { + static_assert(std::totally_ordered_with, + underlying_t>, "Must be totally ordered."); dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::LesserEqual{ - .op1 = dynamic::Column{.name = _name1.str()}, - .op2 = dynamic::Column{.name = _name2.str()}, - }}; + .op1 = make_field(_cond.op1).val, + .op2 = make_field(_cond.op2).val}}; } }; -template -struct ToCondition, Value>> { - static_assert(all_columns_exist>(), "All columns must exist."); - static_assert(std::totally_ordered_with>, - underlying_t>>, - "Must be totally ordered."); - - dynamic::Condition operator()(const auto& _cond) const { - return dynamic::Condition{.val = dynamic::Condition::LesserEqual{ - .op1 = dynamic::Column{.name = _name.str()}, - .op2 = to_value(_cond.op2.val), - }}; - } -}; - -template -struct ToCondition, Col<_name2>>> { - static_assert(all_columns_exist, Col<_name2>>(), - "All columns must exist."); - static_assert(std::totally_ordered_with>, - underlying_t>>, +template +struct ToCondition> { + static_assert(std::totally_ordered_with, + underlying_t>, "Must be totally ordered."); dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::LesserThan{ - .op1 = dynamic::Column{.name = _name1.str()}, - .op2 = dynamic::Column{.name = _name2.str()}, - }}; + .op1 = make_field(_cond.op1).val, + .op2 = make_field(_cond.op2).val}}; } }; -template -struct ToCondition, Value>> { - static_assert(all_columns_exist>(), "All columns must exist."); - static_assert(std::totally_ordered_with>, - underlying_t>>, - "Must be totally ordered."); - - dynamic::Condition operator()(const auto& _cond) const { - return dynamic::Condition{.val = dynamic::Condition::LesserThan{ - .op1 = dynamic::Column{.name = _name.str()}, - .op2 = to_value(_cond.op2.val), - }}; - } -}; - -template -struct ToCondition>> { - static_assert(all_columns_exist>(), "All columns must exist."); +template +struct ToCondition> { static_assert( - std::equality_comparable_with>, + std::equality_comparable_with, underlying_t>>, "Must be equality comparable with a string."); dynamic::Condition operator()(const auto& _cond) const { - return dynamic::Condition{.val = dynamic::Condition::Like{ - .op = dynamic::Column{.name = _name.str()}, - .pattern = to_value(_cond.pattern)}}; + return dynamic::Condition{ + .val = dynamic::Condition::Like{.op = make_field(_cond.op).val, + .pattern = to_value(_cond.pattern)}}; } }; -template -struct ToCondition>> { - static_assert(all_columns_exist>(), "All columns must exist."); - - dynamic::Condition operator()(const auto&) const { +template +struct ToCondition> { + dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::IsNotNull{ - .op = dynamic::Column{.name = _name.str()}}}; + .op = make_field(_cond.op).val}}; } }; -template -struct ToCondition>> { - static_assert(all_columns_exist>(), "All columns must exist."); - - dynamic::Condition operator()(const auto&) const { - return dynamic::Condition{.val = dynamic::Condition::IsNull{ - .op = dynamic::Column{.name = _name.str()}}}; +template +struct ToCondition> { + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{ + .val = dynamic::Condition::IsNull{.op = make_field(_cond.op).val}}; } }; -template -struct ToCondition, Col<_name2>>> { - static_assert(all_columns_exist, Col<_name2>>(), - "All columns must exist."); - static_assert(std::equality_comparable_with>, - underlying_t>>, +template +struct ToCondition> { + dynamic::Condition operator()(const auto& _cond) const { + return dynamic::Condition{ + .val = dynamic::Condition::Not{ + .cond = Ref::make( + ToCondition>{}(_cond.cond))}}; + } +}; + +template +struct ToCondition> { + static_assert(std::equality_comparable_with, + underlying_t>, "Must be equality comparable."); dynamic::Condition operator()(const auto& _cond) const { return dynamic::Condition{.val = dynamic::Condition::NotEqual{ - .op1 = dynamic::Column{.name = _name1.str()}, - .op2 = dynamic::Column{.name = _name2.str()}, - }}; + .op1 = make_field(_cond.op1).val, + .op2 = make_field(_cond.op2).val}}; } }; -template -struct ToCondition, Value>> { - static_assert(all_columns_exist>(), "All columns must exist."); - static_assert(std::equality_comparable_with>, - underlying_t>>, - "Must be equality comparable."); - - dynamic::Condition operator()(const auto& _cond) const { - return dynamic::Condition{.val = dynamic::Condition::NotEqual{ - .op1 = dynamic::Column{.name = _name.str()}, - .op2 = to_value(_cond.op2.val), - }}; - } -}; - -template -struct ToCondition>> { - static_assert(all_columns_exist>(), "All columns must exist."); +template +struct ToCondition> { static_assert( - std::equality_comparable_with>, + std::equality_comparable_with, underlying_t>>, "Must be equality comparable with a string."); dynamic::Condition operator()(const auto& _cond) const { - return dynamic::Condition{.val = dynamic::Condition::NotLike{ - .op = dynamic::Column{.name = _name.str()}, - .pattern = to_value(_cond.pattern)}}; + return dynamic::Condition{ + .val = dynamic::Condition::NotLike{.op = make_field(_cond.op).val, + .pattern = to_value(_cond.pattern)}}; } }; diff --git a/include/sqlgen/transpilation/to_select_from.hpp b/include/sqlgen/transpilation/to_select_from.hpp index b237f27..b0bf6b2 100644 --- a/include/sqlgen/transpilation/to_select_from.hpp +++ b/include/sqlgen/transpilation/to_select_from.hpp @@ -14,6 +14,7 @@ #include "../dynamic/Table.hpp" #include "../internal/collect/vector.hpp" #include "check_aggregations.hpp" +#include "flatten_fields_t.hpp" #include "get_schema.hpp" #include "get_tablename.hpp" #include "make_fields.hpp" @@ -31,9 +32,11 @@ template (), - "The aggregations were not set up correctly. Please check the " - "trace for a more detailed error message."); + static_assert( + check_aggregations, + GroupByType>(), + "The aggregations were not set up correctly. Please check the " + "trace for a more detailed error message."); const auto fields = make_fields( _fields, diff --git a/include/sqlgen/transpilation/to_sql.hpp b/include/sqlgen/transpilation/to_sql.hpp index b4d7cca..a84309d 100644 --- a/include/sqlgen/transpilation/to_sql.hpp +++ b/include/sqlgen/transpilation/to_sql.hpp @@ -77,9 +77,9 @@ struct ToSQL> { }; template + class GroupByType, class OrderByType, class LimitType, class ToType> struct ToSQL> { + OrderByType, LimitType, ToType>> { dynamic::Statement operator()(const auto& _select_from) const { return to_select_from( diff --git a/include/sqlgen/transpilation/to_transpilation_type.hpp b/include/sqlgen/transpilation/to_transpilation_type.hpp new file mode 100644 index 0000000..7c4b243 --- /dev/null +++ b/include/sqlgen/transpilation/to_transpilation_type.hpp @@ -0,0 +1,46 @@ +#ifndef SQLGEN_TRANSPILATION_TO_TRANSPILATION_TYPE_HPP_ +#define SQLGEN_TRANSPILATION_TO_TRANSPILATION_TYPE_HPP_ + +#include +#include + +#include "Value.hpp" + +namespace sqlgen::transpilation { + +template +struct ToTranspilationType; + +template +struct ToTranspilationType { + using Type = Value; + + Type operator()(const T& _val) const noexcept { return make_value(_val); } +}; + +template <> +struct ToTranspilationType { + using Type = Value; + + Type operator()(const char* _val) const noexcept { return make_value(_val); } +}; + +template +struct ToTranspilationType { + using Type = Value; + + Type operator()(const char* _val) const noexcept { return make_value(_val); } +}; + +template +auto to_transpilation_type(const T& _t) { + return ToTranspilationType>{}(_t); +} + +inline auto to_transpilation_type(const char* _t) { + return ToTranspilationType{}(_t); +} + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/to_value.hpp b/include/sqlgen/transpilation/to_value.hpp index 4f0f36e..b0f9dd4 100644 --- a/include/sqlgen/transpilation/to_value.hpp +++ b/include/sqlgen/transpilation/to_value.hpp @@ -14,13 +14,13 @@ template dynamic::Value to_value(const T& _t) { using Type = std::remove_cvref_t; if constexpr (std::is_floating_point_v) { - return dynamic::Float{.val = static_cast(_t)}; + return dynamic::Value{dynamic::Float{.val = static_cast(_t)}}; } else if constexpr (std::is_integral_v) { - return dynamic::Integer{.val = static_cast(_t)}; + return dynamic::Value{dynamic::Integer{.val = static_cast(_t)}}; } else if constexpr (std::is_convertible_v) { - return dynamic::String{.val = std::string(_t)}; + return dynamic::Value{dynamic::String{.val = std::string(_t)}}; } else if constexpr (has_reflection_method) { return to_value(_t.reflection()); diff --git a/include/sqlgen/transpilation/underlying_t.hpp b/include/sqlgen/transpilation/underlying_t.hpp index 92f598e..1afa3ff 100644 --- a/include/sqlgen/transpilation/underlying_t.hpp +++ b/include/sqlgen/transpilation/underlying_t.hpp @@ -2,11 +2,19 @@ #define SQLGEN_TRANSPILATION_UNDERLYINGT_HPP_ #include +#include #include +#include "Aggregation.hpp" +#include "AggregationOp.hpp" #include "Col.hpp" #include "Desc.hpp" +#include "Operation.hpp" #include "Value.hpp" +#include "all_columns_exist.hpp" +#include "dynamic_operator_t.hpp" +#include "is_nullable.hpp" +#include "remove_nullable_t.hpp" #include "remove_reflection_t.hpp" namespace sqlgen::transpilation { @@ -14,8 +22,14 @@ namespace sqlgen::transpilation { template struct Underlying; +template +struct Underlying> { + using Type = typename Underlying>::Type; +}; + template struct Underlying> { + static_assert(all_columns_exist>(), "All columns must exist."); using Type = remove_reflection_t>; }; @@ -24,6 +38,162 @@ struct Underlying>> { using Type = remove_reflection_t>; }; +template +struct Underlying< + T, Operation>> { + using Type = + std::conditional_t>::Type>, + std::optional>, + std::remove_cvref_t>; +}; + +template +struct Underlying>> { + using Operand1Type = typename Underlying>::Type; + + static_assert((true && ... && + std::is_same_v, + remove_nullable_t>::Type>>), + "All inputs into coalesce(...) must have the same type."); + + using Type = std::conditional_t< + (is_nullable_v && ... && + is_nullable_v>::Type>), + std::optional>, + remove_nullable_t>; +}; + +template +struct Underlying>> { + static_assert( + (true && ... && + std::is_same_v>::Type>, + std::string>), + "Must be a string"); + + using Type = + std::conditional_t<(false || ... || + is_nullable_v>::Type>), + std::optional, std::string>; +}; + +template +struct Underlying< + T, Operation> { + using Underlying1 = + typename Underlying>::Type; + using Underlying2 = + typename Underlying>::Type; + using Underlying3 = + typename Underlying>::Type; + + static_assert(std::is_same_v, std::string>, + "Must be a string"); + static_assert(std::is_same_v, std::string>, + "Must be a string"); + static_assert(std::is_same_v, std::string>, + "Must be a string"); + + using Type = std::conditional_t || + is_nullable_v || + is_nullable_v, + std::optional, std::string>; +}; + +template +struct Underlying> { + using Underlying1 = + typename Underlying>::Type; + using Underlying2 = + typename Underlying>::Type; + + static_assert(std::is_integral_v> || + std::is_floating_point_v>, + "Must be a numerical type"); + static_assert(std::is_integral_v, "Must be an integral type"); + + using Type = Underlying1; +}; + +template + requires((num_operands_v<_op>) == 1 && + (operator_category_v<_op>) == OperatorCategory::string) +struct Underlying> { + using Underlying1 = + typename Underlying>::Type; + + static_assert(std::is_same_v, std::string>, + "Must be a string"); + + using StringType = + std::conditional_t, std::optional, + std::string>; + using SizeType = std::conditional_t, + std::optional, size_t>; + using Type = + std::conditional_t<_op == Operator::length, SizeType, StringType>; +}; + +template + requires((num_operands_v<_op>) == 2 && + (operator_category_v<_op>) == OperatorCategory::string) +struct Underlying> { + using Underlying1 = + typename Underlying>::Type; + using Underlying2 = + typename Underlying>::Type; + + static_assert(std::is_same_v, std::string>, + "Must be a string"); + static_assert(std::is_same_v, std::string>, + "Must be a string"); + + using Type = std::conditional_t || + is_nullable_v, + std::optional, std::string>; +}; + +template + requires((num_operands_v<_op>) == 1 && + (operator_category_v<_op>) == OperatorCategory::numerical) +struct Underlying> { + using Underlying1 = + typename Underlying>::Type; + + static_assert(std::is_integral_v> || + std::is_floating_point_v>, + "Must be a numerical type"); + + using Type = Underlying1; +}; + +template + requires((num_operands_v<_op>) == 2 && + (operator_category_v<_op>) == OperatorCategory::numerical) +struct Underlying> { + using Underlying1 = + typename Underlying>::Type; + using Underlying2 = + typename Underlying>::Type; + + static_assert( + requires(remove_nullable_t op1, + remove_nullable_t op2) { op1 + op2; }, + "Binary operations are not possible on these types."); + + using ResultType = std::invoke_result_t< + decltype([](const auto& op1, const auto& op2) { return op1 + op2; }), + remove_nullable_t, remove_nullable_t>; + + using Type = std::conditional_t || + is_nullable_v, + std::optional, ResultType>; +}; + template struct Underlying> { using Type = _Type; diff --git a/src/sqlgen/postgres/to_sql.cpp b/src/sqlgen/postgres/to_sql.cpp index 80798f6..99cda64 100644 --- a/src/sqlgen/postgres/to_sql.cpp +++ b/src/sqlgen/postgres/to_sql.cpp @@ -6,6 +6,7 @@ #include #include +#include "sqlgen/dynamic/Operation.hpp" #include "sqlgen/internal/collect/vector.hpp" #include "sqlgen/internal/strings/strings.hpp" @@ -43,6 +44,8 @@ std::vector get_primary_keys( std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept; +std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept; + std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept; std::string type_to_sql(const dynamic::Type& _type) noexcept; @@ -69,33 +72,29 @@ std::string add_not_null_if_necessary( std::string aggregation_to_sql( const dynamic::Aggregation& _aggregation) noexcept { return _aggregation.val.visit([](const auto& _agg) -> std::string { - std::stringstream stream; - using Type = std::remove_cvref_t; - + std::stringstream stream; if constexpr (std::is_same_v) { - stream << "AVG(" << column_or_value_to_sql(_agg.val) << ")"; + stream << "AVG(" << operation_to_sql(*_agg.val) << ")"; } else if constexpr (std::is_same_v) { - stream << "COUNT(" - << std::string(_agg.val && _agg.distinct ? " DISTINCT " : "") - << (_agg.val ? column_or_value_to_sql(*_agg.val) - : std::string("*")) - << ")"; + const auto val = + std::string(_agg.val && _agg.distinct ? "DISTINCT " : "") + + (_agg.val ? column_or_value_to_sql(*_agg.val) : std::string("*")); + stream << "COUNT(" << val << ")"; } else if constexpr (std::is_same_v) { - stream << "MAX(" << column_or_value_to_sql(_agg.val) << ")"; + stream << "MAX(" << operation_to_sql(*_agg.val) << ")"; } else if constexpr (std::is_same_v) { - stream << "MIN(" << column_or_value_to_sql(_agg.val) << ")"; + stream << "MIN(" << operation_to_sql(*_agg.val) << ")"; } else if constexpr (std::is_same_v) { - stream << "SUM(" << column_or_value_to_sql(_agg.val) << ")"; + stream << "SUM(" << operation_to_sql(*_agg.val) << ")"; } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } - return stream.str(); }); } @@ -116,7 +115,7 @@ std::string column_or_value_to_sql( if constexpr (std::is_same_v) { return wrap_in_quotes(_c.name); } else { - return _c.visit(handle_value); + return _c.val.visit(handle_value); } }); } @@ -129,6 +128,7 @@ std::string condition_to_sql(const dynamic::Condition& _cond) noexcept { template std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { using C = std::remove_cvref_t; + std::stringstream stream; if constexpr (std::is_same_v) { @@ -136,41 +136,44 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { << condition_to_sql(*_condition.cond2) << ")"; } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) << " = " - << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) << " = " + << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) - << " >= " << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) + << " >= " << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) << " > " - << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) << " > " + << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op) << " IS NULL"; + stream << operation_to_sql(_condition.op) << " IS NULL"; } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op) << " IS NOT NULL"; + stream << operation_to_sql(_condition.op) << " IS NOT NULL"; } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) - << " <= " << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) + << " <= " << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) << " < " - << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) << " < " + << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op) << " LIKE " + stream << operation_to_sql(_condition.op) << " LIKE " << column_or_value_to_sql(_condition.pattern); + } else if constexpr (std::is_same_v) { + stream << "NOT (" << condition_to_sql(*_condition.cond) << ")"; + } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) - << " != " << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) + << " != " << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op) << " NOT LIKE " + stream << operation_to_sql(_condition.op) << " NOT LIKE " << column_or_value_to_sql(_condition.pattern); } else if constexpr (std::is_same_v) { @@ -180,7 +183,6 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } - return stream.str(); } @@ -309,14 +311,7 @@ std::string escape_single_quote(const std::string& _str) noexcept { std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept { std::stringstream stream; - stream << _field.val.visit([](const auto& _val) -> std::string { - using Type = std::remove_cvref_t; - if constexpr (std::is_same_v) { - return aggregation_to_sql(_val); - } else { - return column_or_value_to_sql(_val); - } - }); + stream << operation_to_sql(_field.val); if (_field.as) { stream << " AS " << wrap_in_quotes(*_field.as); @@ -369,6 +364,131 @@ std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept { return stream.str(); } +std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept { + using namespace std::ranges::views; + return _stmt.val.visit([](const auto& _s) -> std::string { + using Type = std::remove_cvref_t; + + std::stringstream stream; + + if constexpr (std::is_same_v) { + stream << "abs(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << aggregation_to_sql(_s); + + } else if constexpr (std::is_same_v) { + stream << "cast(" << operation_to_sql(*_s.op1) << " as " + << type_to_sql(_s.target_type) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "coalesce(" + << internal::strings::join( + ", ", internal::collect::vector( + _s.ops | transform([](const auto& _op) { + return operation_to_sql(*_op); + }))) + << ")"; + + } else if constexpr (std::is_same_v) { + stream << "ceil(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_s); + + } else if constexpr (std::is_same_v) { + stream << "(" + << internal::strings::join( + " || ", internal::collect::vector( + _s.ops | transform([](const auto& _op) { + return operation_to_sql(*_op); + }))) + << ")"; + + } else if constexpr (std::is_same_v) { + stream << "cos(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "(" << operation_to_sql(*_s.op1) << ") / (" + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "exp(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "floor(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "length(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "ln(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "log(2.0, " << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "lower(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "ltrim(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "(" << operation_to_sql(*_s.op1) << ") - (" + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "mod(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "(" << operation_to_sql(*_s.op1) << ") * (" + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "(" << operation_to_sql(*_s.op1) << ") + (" + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "replace(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ", " << operation_to_sql(*_s.op3) + << ")"; + + } else if constexpr (std::is_same_v) { + stream << "round(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "rtrim(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "sin(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "sqrt(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "tan(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "trim(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "upper(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_s); + + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + return stream.str(); + }); +} + std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept { using namespace std::ranges::views; @@ -466,10 +586,9 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept { } else if constexpr (std::is_same_v || std::is_same_v) { return "BIGINT"; - } else if constexpr (std::is_same_v) { - return "REAL"; - } else if constexpr (std::is_same_v) { - return "DOUBLE PRECISION"; + } else if constexpr (std::is_same_v || + std::is_same_v) { + return "NUMERIC"; } else if constexpr (std::is_same_v) { return "TEXT"; } else if constexpr (std::is_same_v) { diff --git a/src/sqlgen/sqlite/to_sql.cpp b/src/sqlgen/sqlite/to_sql.cpp index 9c18763..d55b45e 100644 --- a/src/sqlgen/sqlite/to_sql.cpp +++ b/src/sqlgen/sqlite/to_sql.cpp @@ -2,6 +2,7 @@ #include #include +#include "sqlgen/dynamic/Operation.hpp" #include "sqlgen/internal/collect/vector.hpp" #include "sqlgen/internal/strings/strings.hpp" #include "sqlgen/sqlite/Connection.hpp" @@ -36,6 +37,8 @@ std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept; template std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept; +std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept; + std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept; std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept; @@ -47,33 +50,29 @@ std::string update_to_sql(const dynamic::Update& _stmt) noexcept; std::string aggregation_to_sql( const dynamic::Aggregation& _aggregation) noexcept { return _aggregation.val.visit([](const auto& _agg) -> std::string { - std::stringstream stream; - using Type = std::remove_cvref_t; - + std::stringstream stream; if constexpr (std::is_same_v) { - stream << "AVG(" << column_or_value_to_sql(_agg.val) << ")"; + stream << "AVG(" << operation_to_sql(*_agg.val) << ")"; } else if constexpr (std::is_same_v) { - stream << "COUNT(" - << std::string(_agg.val && _agg.distinct ? " DISTINCT " : "") - << (_agg.val ? column_or_value_to_sql(*_agg.val) - : std::string("*")) - << ")"; + const auto val = + std::string(_agg.val && _agg.distinct ? "DISTINCT " : "") + + (_agg.val ? column_or_value_to_sql(*_agg.val) : std::string("*")); + stream << "COUNT(" << val << ")"; } else if constexpr (std::is_same_v) { - stream << "MAX(" << column_or_value_to_sql(_agg.val) << ")"; + stream << "MAX(" << operation_to_sql(*_agg.val) << ")"; } else if constexpr (std::is_same_v) { - stream << "MIN(" << column_or_value_to_sql(_agg.val) << ")"; + stream << "MIN(" << operation_to_sql(*_agg.val) << ")"; } else if constexpr (std::is_same_v) { - stream << "SUM(" << column_or_value_to_sql(_agg.val) << ")"; + stream << "SUM(" << operation_to_sql(*_agg.val) << ")"; } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } - return stream.str(); }); } @@ -94,7 +93,7 @@ std::string column_or_value_to_sql( if constexpr (std::is_same_v) { return "\"" + _c.name + "\""; } else { - return _c.visit(handle_value); + return _c.val.visit(handle_value); } }); } @@ -113,6 +112,7 @@ std::string condition_to_sql(const dynamic::Condition& _cond) noexcept { template std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { using C = std::remove_cvref_t; + std::stringstream stream; if constexpr (std::is_same_v) { @@ -120,41 +120,44 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { << condition_to_sql(*_condition.cond2) << ")"; } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) << " = " - << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) << " = " + << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) - << " >= " << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) + << " >= " << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) << " > " - << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) << " > " + << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op) << " IS NULL"; + stream << operation_to_sql(_condition.op) << " IS NULL"; } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op) << " IS NOT NULL"; + stream << operation_to_sql(_condition.op) << " IS NOT NULL"; } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) - << " <= " << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) + << " <= " << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) << " < " - << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) << " < " + << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op) << " LIKE " + stream << operation_to_sql(_condition.op) << " LIKE " << column_or_value_to_sql(_condition.pattern); + } else if constexpr (std::is_same_v) { + stream << "NOT (" << condition_to_sql(*_condition.cond) << ")"; + } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op1) - << " != " << column_or_value_to_sql(_condition.op2); + stream << operation_to_sql(_condition.op1) + << " != " << operation_to_sql(_condition.op2); } else if constexpr (std::is_same_v) { - stream << column_or_value_to_sql(_condition.op) << " NOT LIKE " + stream << operation_to_sql(_condition.op) << " NOT LIKE " << column_or_value_to_sql(_condition.pattern); } else if constexpr (std::is_same_v) { @@ -164,7 +167,6 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept { } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } - return stream.str(); } @@ -280,14 +282,7 @@ std::string escape_single_quote(const std::string& _str) noexcept { std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept { std::stringstream stream; - stream << _field.val.visit([](const auto& _val) -> std::string { - using Type = std::remove_cvref_t; - if constexpr (std::is_same_v) { - return aggregation_to_sql(_val); - } else { - return column_or_value_to_sql(_val); - } - }); + stream << operation_to_sql(_field.val); if (_field.as) { stream << " AS " << "\"" << *_field.as << "\""; @@ -329,6 +324,131 @@ std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept { return stream.str(); } +std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept { + using namespace std::ranges::views; + return _stmt.val.visit([](const auto& _s) -> std::string { + using Type = std::remove_cvref_t; + + std::stringstream stream; + + if constexpr (std::is_same_v) { + stream << "abs(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << aggregation_to_sql(_s); + + } else if constexpr (std::is_same_v) { + stream << "cast(" << operation_to_sql(*_s.op1) << " as " + << type_to_sql(_s.target_type) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "coalesce(" + << internal::strings::join( + ", ", internal::collect::vector( + _s.ops | transform([](const auto& _op) { + return operation_to_sql(*_op); + }))) + << ")"; + + } else if constexpr (std::is_same_v) { + stream << "ceil(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_s); + + } else if constexpr (std::is_same_v) { + stream << "(" + << internal::strings::join( + " || ", internal::collect::vector( + _s.ops | transform([](const auto& _op) { + return operation_to_sql(*_op); + }))) + << ")"; + + } else if constexpr (std::is_same_v) { + stream << "cos(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "(" << operation_to_sql(*_s.op1) << ") / (" + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "exp(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "floor(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "length(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "ln(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "log2(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "lower(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "ltrim(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "(" << operation_to_sql(*_s.op1) << ") - (" + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "mod(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "(" << operation_to_sql(*_s.op1) << ") * (" + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "(" << operation_to_sql(*_s.op1) << ") + (" + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "replace(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ", " << operation_to_sql(*_s.op3) + << ")"; + + } else if constexpr (std::is_same_v) { + stream << "round(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "rtrim(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "sin(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "sqrt(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "tan(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "trim(" << operation_to_sql(*_s.op1) << ", " + << operation_to_sql(*_s.op2) << ")"; + + } else if constexpr (std::is_same_v) { + stream << "upper(" << operation_to_sql(*_s.op1) << ")"; + + } else if constexpr (std::is_same_v) { + stream << column_or_value_to_sql(_s); + + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + return stream.str(); + }); +} + std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept { return std::string(_p.primary ? " PRIMARY KEY" : "") + std::string(_p.nullable ? "" : " NOT NULL"); diff --git a/tests/postgres/test_group_by_with_operations.cpp b/tests/postgres/test_group_by_with_operations.cpp new file mode 100644 index 0000000..04bc1b9 --- /dev/null +++ b/tests/postgres/test_group_by_with_operations.cpp @@ -0,0 +1,72 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include +#include + +namespace test_group_by_with_operations { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(postgres, test_group_by) { + static_assert(std::ranges::input_range>, + "Must be an input range."); + + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + + struct Children { + std::string last_name; + std::string last_name_trimmed; + double avg_age; + double max_age_plus_one; + double min_age_plus_one; + }; + + const auto get_children = + select_from( + "last_name"_c, trim("last_name"_c).as<"last_name_trimmed">(), + max(cast("age"_c) + 1.0).as<"max_age_plus_one">(), + (min(cast("age"_c)) + 1.0).as<"min_age_plus_one">(), + round(avg(cast("age"_c))).as<"avg_age">()) | + where("age"_c < 18) | group_by("last_name"_c) | to>; + + const auto children = postgres::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(get_children) + .value(); + + EXPECT_EQ(children.size(), 1); + EXPECT_EQ(children.at(0).last_name, "Simpson"); + EXPECT_EQ(children.at(0).last_name_trimmed, "Simpson"); + EXPECT_EQ(children.at(0).avg_age, 6.0); + EXPECT_EQ(children.at(0).max_age_plus_one, 11.0); + EXPECT_EQ(children.at(0).min_age_plus_one, 1.0); +} + +} // namespace test_group_by_with_operations + +#endif diff --git a/tests/postgres/test_operations.cpp b/tests/postgres/test_operations.cpp new file mode 100644 index 0000000..635ddc4 --- /dev/null +++ b/tests/postgres/test_operations.cpp @@ -0,0 +1,83 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include +#include + +namespace test_operations { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(postgres, test_operations) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + + struct Children { + int id_plus_age; + int age_times_2; + int id_plus_2_minus_age; + int age_mod_3; + int abs_age; + double exp_age; + double sqrt_age; + size_t length_first_name; + std::string full_name; + std::string first_name_lower; + std::string first_name_upper; + std::string first_name_replaced; + }; + + const auto get_children = + select_from( + ("id"_c + "age"_c) | as<"id_plus_age">, + ("age"_c * 2) | as<"age_times_2">, ("age"_c % 3) | as<"age_mod_3">, + abs("age"_c * (-1)) | as<"abs_age">, + round(exp(cast("age"_c)), 2) | as<"exp_age">, + round(sqrt(cast("age"_c)), 2) | as<"sqrt_age">, + ("id"_c + 2 - "age"_c) | as<"id_plus_2_minus_age">, + length(trim("first_name"_c)) | as<"length_first_name">, + concat(ltrim("first_name"_c), " ", rtrim("last_name"_c)) | + as<"full_name">, + upper(rtrim(concat("first_name"_c, " "))) | as<"first_name_upper">, + lower(ltrim(concat(" ", "first_name"_c))) | as<"first_name_lower">, + replace("first_name"_c, "Bart", "Hugo") | as<"first_name_replaced">) | + where("age"_c < 18) | order_by("age"_c.desc()) | + to>; + + const auto children = postgres::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(get_children) + .value(); + + const std::string expected = + R"([{"id_plus_age":11,"age_times_2":20,"id_plus_2_minus_age":-7,"age_mod_3":1,"abs_age":10,"exp_age":22026.47,"sqrt_age":3.16,"length_first_name":4,"full_name":"Bart Simpson","first_name_lower":"bart","first_name_upper":"BART","first_name_replaced":"Hugo"},{"id_plus_age":10,"age_times_2":16,"id_plus_2_minus_age":-4,"age_mod_3":2,"abs_age":8,"exp_age":2980.96,"sqrt_age":2.83,"length_first_name":4,"full_name":"Lisa Simpson","first_name_lower":"lisa","first_name_upper":"LISA","first_name_replaced":"Lisa"},{"id_plus_age":3,"age_times_2":0,"id_plus_2_minus_age":5,"age_mod_3":0,"abs_age":0,"exp_age":1.0,"sqrt_age":0.0,"length_first_name":6,"full_name":"Maggie Simpson","first_name_lower":"maggie","first_name_upper":"MAGGIE","first_name_replaced":"Maggie"}])"; + + EXPECT_EQ(rfl::json::write(children), expected); +} + +} // namespace test_operations + +#endif diff --git a/tests/postgres/test_operations_with_nullable.cpp b/tests/postgres/test_operations_with_nullable.cpp new file mode 100644 index 0000000..5aedfdd --- /dev/null +++ b/tests/postgres/test_operations_with_nullable.cpp @@ -0,0 +1,69 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include +#include + +namespace test_operations_with_nullable { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::optional last_name; + std::optional age; +}; + +TEST(postgres, test_operations_with_nullable) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Hugo", .age = 10}, + Person{.id = 3, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 4, .first_name = "Maggie", .last_name = "Simpson", .age = 0}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + + struct Children { + std::optional id_plus_age; + std::optional age_times_2; + std::optional id_plus_2_minus_age; + std::optional full_name; + std::string last_name_or_none; + }; + + const auto get_children = + select_from( + ("id"_c + "age"_c) | as<"id_plus_age">, + ("age"_c * 2) | as<"age_times_2">, + ("id"_c + 2 - "age"_c) | as<"id_plus_2_minus_age">, + concat(upper("last_name"_c), ", ", "first_name"_c) | as<"full_name">, + coalesce("last_name"_c, "none") | as<"last_name_or_none">) | + where("age"_c < 18) | to>; + + const auto children = postgres::connect(credentials) + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(get_children) + .value(); + + const std::string expected = + R"([{"id_plus_age":11,"age_times_2":20,"id_plus_2_minus_age":-7,"full_name":"SIMPSON, Bart","last_name_or_none":"Simpson"},{"id_plus_age":12,"age_times_2":20,"id_plus_2_minus_age":-6,"last_name_or_none":"none"},{"id_plus_age":11,"age_times_2":16,"id_plus_2_minus_age":-3,"full_name":"SIMPSON, Lisa","last_name_or_none":"Simpson"},{"id_plus_age":4,"age_times_2":0,"id_plus_2_minus_age":6,"full_name":"SIMPSON, Maggie","last_name_or_none":"Simpson"}])"; + + EXPECT_EQ(rfl::json::write(children), expected); +} + +} // namespace test_operations_with_nullable + +#endif diff --git a/tests/postgres/test_where.cpp b/tests/postgres/test_where.cpp index ddaed0a..4b00135 100644 --- a/tests/postgres/test_where.cpp +++ b/tests/postgres/test_where.cpp @@ -41,7 +41,7 @@ TEST(postgres, test_where) { sqlgen::write(conn, people1).value(); const auto query = sqlgen::read> | - where("age"_c < 18 and "first_name"_c != "Hugo") | + where("age"_c < 18 and not("first_name"_c == "Hugo")) | order_by("age"_c); const auto people2 = query(conn).value(); diff --git a/tests/postgres/test_where_with_operations.cpp b/tests/postgres/test_where_with_operations.cpp new file mode 100644 index 0000000..b24f39f --- /dev/null +++ b/tests/postgres/test_where_with_operations.cpp @@ -0,0 +1,57 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_where_with_operations { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(postgres, test_where_with_operations) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + using namespace sqlgen; + + const auto conn = + sqlgen::postgres::connect(credentials).and_then(drop | if_exists); + + sqlgen::write(conn, people1).value(); + + const auto query = sqlgen::read> | + where("age"_c * 2 + 4 < 40 and "first_name"_c != "Hugo") | + order_by("age"_c); + + const auto people2 = query(conn).value(); + + const std::string expected = + R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10}])"; + + EXPECT_EQ(rfl::json::write(people2), expected); +} + +} // namespace test_where_with_operations + +#endif diff --git a/tests/sqlite/test_group_by_with_operations.cpp b/tests/sqlite/test_group_by_with_operations.cpp new file mode 100644 index 0000000..833b80e --- /dev/null +++ b/tests/sqlite/test_group_by_with_operations.cpp @@ -0,0 +1,62 @@ + +#include + +#include +#include +#include +#include +#include +#include + +namespace test_group_by_with_operations { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(sqlite, test_group_by_with_operations) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}}); + + using namespace sqlgen; + + struct Children { + std::string last_name; + std::string last_name_trimmed; + double avg_age; + double max_age_plus_one; + double min_age_plus_one; + }; + + const auto get_children = + select_from( + "last_name"_c, trim("last_name"_c).as<"last_name_trimmed">(), + max(cast("age"_c) + 1.0).as<"max_age_plus_one">(), + (min(cast("age"_c)) + 1.0).as<"min_age_plus_one">(), + round(avg(cast("age"_c))).as<"avg_age">()) | + where("age"_c < 18) | group_by("last_name"_c) | to>; + + const auto children = sqlite::connect() + .and_then(drop | if_exists) + .and_then(write(std::ref(people1))) + .and_then(get_children) + .value(); + + EXPECT_EQ(children.size(), 1); + EXPECT_EQ(children.at(0).last_name, "Simpson"); + EXPECT_EQ(children.at(0).last_name_trimmed, "Simpson"); + EXPECT_EQ(children.at(0).avg_age, 6.0); + EXPECT_EQ(children.at(0).max_age_plus_one, 11.0); + EXPECT_EQ(children.at(0).min_age_plus_one, 1.0); +} + +} // namespace test_group_by_with_operations + diff --git a/tests/sqlite/test_operations.cpp b/tests/sqlite/test_operations.cpp new file mode 100644 index 0000000..1a37061 --- /dev/null +++ b/tests/sqlite/test_operations.cpp @@ -0,0 +1,75 @@ + +#include + +#include +#include +#include +#include +#include +#include + +namespace test_operations { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(sqlite, test_operations) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}}); + + using namespace sqlgen; + + struct Children { + int id_plus_age; + int age_times_2; + int id_plus_2_minus_age; + int age_mod_3; + int abs_age; + double exp_age; + double sqrt_age; + size_t length_first_name; + std::string full_name; + std::string first_name_lower; + std::string first_name_upper; + std::string first_name_replaced; + }; + + const auto get_children = + select_from( + ("id"_c + "age"_c) | as<"id_plus_age">, + ("age"_c * 2) | as<"age_times_2">, ("age"_c % 3) | as<"age_mod_3">, + abs("age"_c * (-1)) | as<"abs_age">, + round(exp(cast("age"_c)), 2) | as<"exp_age">, + round(sqrt(cast("age"_c)), 2) | as<"sqrt_age">, + ("id"_c + 2 - "age"_c) | as<"id_plus_2_minus_age">, + length(trim("first_name"_c)) | as<"length_first_name">, + concat(ltrim("first_name"_c), " ", rtrim("last_name"_c)) | + as<"full_name">, + upper(rtrim(concat("first_name"_c, " "))) | as<"first_name_upper">, + lower(ltrim(concat(" ", "first_name"_c))) | as<"first_name_lower">, + replace("first_name"_c, "Bart", "Hugo") | as<"first_name_replaced">) | + where("age"_c < 18) | order_by("age"_c.desc()) | + to>; + + const auto children = sqlite::connect() + .and_then(write(std::ref(people1))) + .and_then(get_children) + .value(); + + const std::string expected = + R"([{"id_plus_age":11,"age_times_2":20,"id_plus_2_minus_age":-7,"age_mod_3":1,"abs_age":10,"exp_age":22026.47,"sqrt_age":3.16,"length_first_name":4,"full_name":"Bart Simpson","first_name_lower":"bart","first_name_upper":"BART","first_name_replaced":"Hugo"},{"id_plus_age":10,"age_times_2":16,"id_plus_2_minus_age":-4,"age_mod_3":2,"abs_age":8,"exp_age":2980.96,"sqrt_age":2.83,"length_first_name":4,"full_name":"Lisa Simpson","first_name_lower":"lisa","first_name_upper":"LISA","first_name_replaced":"Lisa"},{"id_plus_age":3,"age_times_2":0,"id_plus_2_minus_age":5,"age_mod_3":0,"abs_age":0,"exp_age":1.0,"sqrt_age":0.0,"length_first_name":6,"full_name":"Maggie Simpson","first_name_lower":"maggie","first_name_upper":"MAGGIE","first_name_replaced":"Maggie"}])"; + + EXPECT_EQ(rfl::json::write(children), expected); +} + +} // namespace test_operations + diff --git a/tests/sqlite/test_operations_with_nullable.cpp b/tests/sqlite/test_operations_with_nullable.cpp new file mode 100644 index 0000000..df80928 --- /dev/null +++ b/tests/sqlite/test_operations_with_nullable.cpp @@ -0,0 +1,62 @@ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace test_operations_with_nullable { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::optional last_name; + std::optional age; +}; + +TEST(sqlite, test_operations_with_nullable) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Hugo", .age = 10}, + Person{.id = 3, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 4, .first_name = "Maggie", .last_name = "Simpson", .age = 0}}); + + using namespace sqlgen; + + struct Children { + std::optional id_plus_age; + std::optional age_times_2; + std::optional id_plus_2_minus_age; + std::optional full_name; + std::string last_name_or_none; + }; + + const auto get_children = + select_from( + ("id"_c + "age"_c) | as<"id_plus_age">, + ("age"_c * 2) | as<"age_times_2">, + ("id"_c + 2 - "age"_c) | as<"id_plus_2_minus_age">, + concat(upper("last_name"_c), ", ", "first_name"_c) | as<"full_name">, + coalesce(upper("last_name"_c), "none") | as<"last_name_or_none">) | + where("age"_c < 18) | to>; + + const auto children = sqlite::connect() + .and_then(write(std::ref(people1))) + .and_then(get_children) + .value(); + + const std::string expected = + R"([{"id_plus_age":11,"age_times_2":20,"id_plus_2_minus_age":-7,"full_name":"SIMPSON, Bart","last_name_or_none":"SIMPSON"},{"id_plus_age":12,"age_times_2":20,"id_plus_2_minus_age":-6,"last_name_or_none":"none"},{"id_plus_age":11,"age_times_2":16,"id_plus_2_minus_age":-3,"full_name":"SIMPSON, Lisa","last_name_or_none":"SIMPSON"},{"id_plus_age":4,"age_times_2":0,"id_plus_2_minus_age":6,"full_name":"SIMPSON, Maggie","last_name_or_none":"SIMPSON"}])"; + + EXPECT_EQ(rfl::json::write(children), expected); +} + +} // namespace test_operations_with_nullable + diff --git a/tests/sqlite/test_where.cpp b/tests/sqlite/test_where.cpp index 57dbc1a..8baef74 100644 --- a/tests/sqlite/test_where.cpp +++ b/tests/sqlite/test_where.cpp @@ -33,7 +33,7 @@ TEST(sqlite, test_where) { using namespace sqlgen; const auto query = sqlgen::read> | - where("age"_c < 18 and "first_name"_c != "Hugo") | + where("age"_c < 18 and not("first_name"_c == "Hugo")) | order_by("age"_c); const auto people2 = query(conn).value(); diff --git a/tests/sqlite/test_where_with_nullable_operations.cpp b/tests/sqlite/test_where_with_nullable_operations.cpp new file mode 100644 index 0000000..38bc22d --- /dev/null +++ b/tests/sqlite/test_where_with_nullable_operations.cpp @@ -0,0 +1,48 @@ +#include + +#include +#include +#include +#include +#include +#include + +namespace test_where_with_nullable_operations { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + std::optional age; +}; + +TEST(sqlite, test_where_with_nullable_operations) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto conn = sqlgen::sqlite::connect(); + + sqlgen::write(conn, people1); + + using namespace sqlgen; + + const auto query = sqlgen::read> | + where("age"_c * 2 + 4 < 40 and "first_name"_c != "Hugo") | + order_by("age"_c); + + const auto people2 = query(conn).value(); + + const std::string expected = + R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10}])"; + + EXPECT_EQ(rfl::json::write(people2), expected); +} + +} // namespace test_where_with_nullable_operations diff --git a/tests/sqlite/test_where_with_operations.cpp b/tests/sqlite/test_where_with_operations.cpp new file mode 100644 index 0000000..f4eaf77 --- /dev/null +++ b/tests/sqlite/test_where_with_operations.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include +#include +#include +#include + +namespace test_where_with_operations { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(sqlite, test_where_with_operations) { + const auto people1 = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}, + Person{ + .id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}}); + + const auto conn = sqlgen::sqlite::connect(); + + sqlgen::write(conn, people1); + + using namespace sqlgen; + + const auto query = sqlgen::read> | + where("age"_c * 2 + 4 < 40 and "first_name"_c != "Hugo") | + order_by("age"_c); + + const auto people2 = query(conn).value(); + + const std::string expected = + R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10}])"; + + EXPECT_EQ(rfl::json::write(people2), expected); +} + +} // namespace test_where_with_operations diff --git a/vcpkg.json b/vcpkg.json index 5405719..dadc80a 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -20,7 +20,8 @@ "dependencies": [ { "name": "sqlite3", - "version>=": "3.49.1" + "version>=": "3.49.1", + "features": ["math"] } ] },