Added numerous operations like concat(...), coalesce(...), abs(...), etc (#20)

This commit is contained in:
Dr. Patrick Urbanke (劉自成)
2025-06-22 18:05:48 +02:00
committed by GitHub
parent a6bad187fe
commit 30ba548f1e
45 changed files with 2928 additions and 593 deletions

View File

@@ -227,6 +227,17 @@ const auto query = read<std::vector<Person>> |
const auto query = read<std::vector<Person>> |
where("age"_c == "Homer");
// Compile-time error: "age" must be aggregated or included in GROUP BY
const auto query = select_from<Person>(
"last_name"_c,
"age"_c
) | group_by("last_name"_c);
// Compile-time error: Cannot add string and int
const auto query = select_from<Person>(
"last_name"_c + "age"_c
);
// Runtime protection against SQL injection
std::vector<Person> get_people(const auto& conn,
const sqlgen::AlphaNumeric& first_name) {

View File

@@ -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

249
docs/other_operations.md Normal file
View File

@@ -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<double>("age"_c)), 2) | as<"exp_age">
round(sqrt(cast<double>("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<double>("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<T>`), the result will also be nullable if any operand is nullable. For example, adding two `std::optional<int>` columns will yield a `std::optional<int>`. 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<T>`), 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<ResultType>`).
- 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<T>`).
- 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<int>{}, std::optional<int>{}) // -> std::optional<int>
// At least one argument non-nullable: result is non-nullable
coalesce(std::optional<int>{}, 42) // -> int
coalesce(42, std::optional<int>{}) // -> int
// All arguments non-nullable: result is non-nullable
coalesce(1, 2) // -> int
// Mixed string example
coalesce(std::optional<std::string>{}, "default") // -> std::string
// Compile-time error: mismatched types
// coalesce(std::optional<int>{}, std::optional<double>{}) // 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<Person>(
("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<double>("age"_c)), 2) | as<"exp_age">,
round(sqrt(cast<double>("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<std::vector<Children>>;
```
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.

View File

@@ -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"

View File

@@ -5,48 +5,61 @@
#include <type_traits>
#include "col.hpp"
#include "transpilation/aggregations.hpp"
#include "transpilation/Aggregation.hpp"
#include "transpilation/AggregationOp.hpp"
#include "transpilation/to_transpilation_type.hpp"
namespace sqlgen {
template <rfl::internal::StringLiteral _name>
auto avg(const Col<_name>&) {
return transpilation::aggregations::Avg<transpilation::Col<_name>>{
.val = transpilation::Col<_name>{}};
template <class T>
auto avg(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Aggregation<transpilation::AggregationOp::avg, Type>{
.val = transpilation::to_transpilation_type(_t)};
}
inline auto count() {
return transpilation::aggregations::Count<transpilation::aggregations::All>{};
return transpilation::Aggregation<transpilation::AggregationOp::count,
transpilation::All>{};
}
template <rfl::internal::StringLiteral _name>
auto count(const Col<_name>&) {
return transpilation::aggregations::Count<transpilation::Col<_name>>{
return transpilation::Aggregation<transpilation::AggregationOp::count,
transpilation::Col<_name>>{
.val = transpilation::Col<_name>{}};
}
template <rfl::internal::StringLiteral _name>
auto count_distinct(const Col<_name>&) {
return transpilation::aggregations::Count<transpilation::Col<_name>>{
return transpilation::Aggregation<transpilation::AggregationOp::count,
transpilation::Col<_name>>{
.val = transpilation::Col<_name>{}, .distinct = true};
}
template <rfl::internal::StringLiteral _name>
auto max(const Col<_name>&) {
return transpilation::aggregations::Max<transpilation::Col<_name>>{
.val = transpilation::Col<_name>{}};
template <class T>
auto max(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Aggregation<transpilation::AggregationOp::max, Type>{
.val = transpilation::to_transpilation_type(_t)};
}
template <rfl::internal::StringLiteral _name>
auto min(const Col<_name>&) {
return transpilation::aggregations::Min<transpilation::Col<_name>>{
.val = transpilation::Col<_name>{}};
template <class T>
auto min(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Aggregation<transpilation::AggregationOp::min, Type>{
.val = transpilation::to_transpilation_type(_t)};
}
template <rfl::internal::StringLiteral _name>
auto sum(const Col<_name>&) {
return transpilation::aggregations::Sum<transpilation::Col<_name>>{
.val = transpilation::Col<_name>{}};
template <class T>
auto sum(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Aggregation<transpilation::AggregationOp::sum, Type>{
.val = transpilation::to_transpilation_type(_t)};
}
} // namespace sqlgen

View File

@@ -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<transpilation::Col<_name>, std::string>{.to =
_to};
}
template <class T>
friend auto operator==(const Col&, const T& _t) {
return transpilation::make_condition(transpilation::conditions::equal(
transpilation::Col<_name>{}, transpilation::to_transpilation_type(_t)));
}
template <class T>
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 <class T>
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 <class T>
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 <class T>
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 <class T>
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 <class T>
friend auto operator/(const Col&, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::divides,
transpilation::Col<_name>, OtherType>{
.operand1 = transpilation::Col<_name>{},
.operand2 = transpilation::to_transpilation_type(_op2)};
}
template <class T>
friend auto operator-(const Col&, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::minus,
transpilation::Col<_name>, OtherType>{
.operand1 = transpilation::Col<_name>{},
.operand2 = transpilation::to_transpilation_type(_op2)};
}
template <class T>
friend auto operator%(const Col&, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::mod,
transpilation::Col<_name>, OtherType>{
.operand1 = transpilation::Col<_name>{},
.operand2 = transpilation::to_transpilation_type(_op2)};
}
template <class T>
friend auto operator*(const Col&, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::multiplies,
transpilation::Col<_name>, OtherType>{
.operand1 = transpilation::Col<_name>{},
.operand2 = transpilation::to_transpilation_type(_op2)};
}
template <class T>
friend auto operator+(const Col&, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::plus,
transpilation::Col<_name>, OtherType>{
.operand1 = transpilation::Col<_name>{},
.operand2 = transpilation::to_transpilation_type(_op2)};
}
};
template <rfl::internal::StringLiteral _name>
@@ -87,83 +187,18 @@ auto operator"" _c() {
return Col<_name>{};
}
template <rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
auto operator==(const Col<_name1>&, const Col<_name2>&) {
return transpilation::make_condition(transpilation::conditions::equal(
transpilation::Col<_name1>{}, transpilation::Col<_name2>{}));
}
namespace transpilation {
template <rfl::internal::StringLiteral _name1, class T>
auto operator==(const Col<_name1>&, const T& _t) {
return transpilation::make_condition(transpilation::conditions::equal(
transpilation::Col<_name1>{}, transpilation::make_value(_t)));
}
template <rfl::internal::StringLiteral _name>
struct ToTranspilationType<sqlgen::Col<_name>> {
using Type = transpilation::Col<_name>;
template <rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
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 <rfl::internal::StringLiteral _name1, class T>
auto operator!=(const Col<_name1>&, const T& _t) {
return transpilation::make_condition(transpilation::conditions::not_equal(
transpilation::Col<_name1>{}, transpilation::make_value(_t)));
}
template <rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
auto operator<(const Col<_name1>&, const Col<_name2>&) {
return transpilation::make_condition(transpilation::conditions::lesser_than(
transpilation::Col<_name1>{}, transpilation::Col<_name2>{}));
}
template <rfl::internal::StringLiteral _name1, class T>
auto operator<(const Col<_name1>&, const T& _t) {
return transpilation::make_condition(transpilation::conditions::lesser_than(
transpilation::Col<_name1>{}, transpilation::make_value(_t)));
}
template <rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
auto operator<=(const Col<_name1>&, const Col<_name2>&) {
return transpilation::make_condition(transpilation::conditions::lesser_equal(
transpilation::Col<_name1>{}, transpilation::Col<_name2>{}));
}
template <rfl::internal::StringLiteral _name1, class T>
auto operator<=(const Col<_name1>&, const T& _t) {
return transpilation::make_condition(transpilation::conditions::lesser_equal(
transpilation::Col<_name1>{}, transpilation::make_value(_t)));
}
template <rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
auto operator>(const Col<_name1>&, const Col<_name2>&) {
return transpilation::make_condition(transpilation::conditions::greater_than(
transpilation::Col<_name1>{}, transpilation::Col<_name2>{}));
}
template <rfl::internal::StringLiteral _name1, class T>
auto operator>(const Col<_name1>&, const T& _t) {
return transpilation::make_condition(transpilation::conditions::greater_than(
transpilation::Col<_name1>{}, transpilation::make_value(_t)));
}
template <rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
auto operator>=(const Col<_name1>&, const Col<_name2>&) {
return transpilation::make_condition(transpilation::conditions::greater_equal(
transpilation::Col<_name1>{}, transpilation::Col<_name2>{}));
}
template <rfl::internal::StringLiteral _name1, class T>
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

View File

@@ -1,44 +1,11 @@
#ifndef SQLGEN_DYNAMIC_AGGREGATION_HPP_
#define SQLGEN_DYNAMIC_AGGREGATION_HPP_
#include <optional>
#include <rfl.hpp>
#include <string>
#include <vector>
#include "Column.hpp"
#include "ColumnOrValue.hpp"
#include "Operation.hpp"
namespace sqlgen::dynamic {
struct Aggregation {
struct Avg {
ColumnOrValue val;
};
struct Count {
std::optional<Column> 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

View File

@@ -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<Condition> 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; }

View File

@@ -0,0 +1,175 @@
#ifndef SQLGEN_DYNAMIC_OPERATION_HPP_
#define SQLGEN_DYNAMIC_OPERATION_HPP_
#include <optional>
#include <rfl.hpp>
#include <string>
#include <vector>
#include "../Ref.hpp"
#include "Column.hpp"
#include "Type.hpp"
#include "Value.hpp"
namespace sqlgen::dynamic {
struct Operation {
struct Abs {
Ref<Operation> op1;
};
struct Aggregation {
struct Avg {
Ref<Operation> val;
};
struct Count {
std::optional<Column> val;
bool distinct = false;
};
struct Max {
Ref<Operation> val;
};
struct Min {
Ref<Operation> val;
};
struct Sum {
Ref<Operation> val;
};
using ReflectionType = rfl::TaggedUnion<"what", Avg, Count, Max, Min, Sum>;
const ReflectionType& reflection() const { return val; }
ReflectionType val;
};
struct Cast {
Ref<Operation> op1;
Type target_type;
};
struct Ceil {
Ref<Operation> op1;
};
struct Coalesce {
std::vector<Ref<Operation>> ops;
};
struct Concat {
std::vector<Ref<Operation>> ops;
};
struct Cos {
Ref<Operation> op1;
};
struct Divides {
Ref<Operation> op1;
Ref<Operation> op2;
};
struct Exp {
Ref<Operation> op1;
};
struct Floor {
Ref<Operation> op1;
};
struct Length {
Ref<Operation> op1;
};
struct Ln {
Ref<Operation> op1;
};
struct Lower {
Ref<Operation> op1;
};
struct LTrim {
Ref<Operation> op1;
Ref<Operation> op2;
};
struct Log2 {
Ref<Operation> op1;
};
struct Minus {
Ref<Operation> op1;
Ref<Operation> op2;
};
struct Mod {
Ref<Operation> op1;
Ref<Operation> op2;
};
struct Multiplies {
Ref<Operation> op1;
Ref<Operation> op2;
};
struct Plus {
Ref<Operation> op1;
Ref<Operation> op2;
};
struct Replace {
Ref<Operation> op1;
Ref<Operation> op2;
Ref<Operation> op3;
};
struct Round {
Ref<Operation> op1;
Ref<Operation> op2;
};
struct RTrim {
Ref<Operation> op1;
Ref<Operation> op2;
};
struct Sin {
Ref<Operation> op1;
};
struct Sqrt {
Ref<Operation> op1;
};
struct Tan {
Ref<Operation> op1;
};
struct Trim {
Ref<Operation> op1;
Ref<Operation> op2;
};
struct Upper {
Ref<Operation> 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

View File

@@ -6,20 +6,18 @@
#include <string>
#include <vector>
#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<std::string> as;
};

View File

@@ -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

View File

@@ -0,0 +1,229 @@
#ifndef SQLGEN_OPERATIONS_HPP_
#define SQLGEN_OPERATIONS_HPP_
#include <rfl.hpp>
#include <type_traits>
#include "col.hpp"
#include "transpilation/Operation.hpp"
#include "transpilation/Operator.hpp"
#include "transpilation/to_transpilation_type.hpp"
namespace sqlgen {
template <class T>
auto abs(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::abs, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class TargetType, class T>
auto cast(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<
transpilation::Operator::cast, Type,
transpilation::TypeHolder<std::remove_cvref_t<TargetType>>>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T>
auto ceil(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::ceil, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class... Ts>
auto coalesce(const Ts&... _ts) {
static_assert(sizeof...(_ts) > 1,
"coalesce(...) must have at least two inputs.");
using Type = rfl::Tuple<typename transpilation::ToTranspilationType<
std::remove_cvref_t<Ts>>::Type...>;
return transpilation::Operation<transpilation::Operator::coalesce, Type>{
.operand1 = Type(transpilation::to_transpilation_type(_ts)...)};
}
template <class... Ts>
auto concat(const Ts&... _ts) {
static_assert(sizeof...(_ts) > 0,
"concat(...) must have at least one input.");
using Type = rfl::Tuple<typename transpilation::ToTranspilationType<
std::remove_cvref_t<Ts>>::Type...>;
return transpilation::Operation<transpilation::Operator::concat, Type>{
.operand1 = Type(transpilation::to_transpilation_type(_ts)...)};
}
template <class T>
auto cos(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::cos, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T>
auto exp(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::exp, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T>
auto floor(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::floor, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T>
auto length(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::length, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T>
auto ln(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::ln, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T>
auto log2(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::log2, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T>
auto lower(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::lower, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T, class U>
auto ltrim(const T& _t, const U& _u) {
using Type1 =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
using Type2 =
typename transpilation::ToTranspilationType<std::remove_cvref_t<U>>::Type;
return transpilation::Operation<transpilation::Operator::ltrim, Type1, Type2>{
.operand1 = transpilation::to_transpilation_type(_t),
.operand2 = transpilation::to_transpilation_type(_u)};
}
template <class T>
auto ltrim(const T& _t) {
return ltrim(_t, std::string(" "));
}
template <class StringType, class FromType, class ToType>
auto replace(const StringType& _str, const FromType& _from, const ToType& _to) {
using Type1 = typename transpilation::ToTranspilationType<
std::remove_cvref_t<StringType>>::Type;
using Type2 = typename transpilation::ToTranspilationType<
std::remove_cvref_t<FromType>>::Type;
using Type3 = typename transpilation::ToTranspilationType<
std::remove_cvref_t<ToType>>::Type;
return transpilation::Operation<transpilation::Operator::replace, Type1,
Type2, Type3>{
.operand1 = transpilation::to_transpilation_type(_str),
.operand2 = transpilation::to_transpilation_type(_from),
.operand3 = transpilation::to_transpilation_type(_to)};
}
template <class T, class U>
auto round(const T& _t, const U& _u) {
using Type1 =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
using Type2 =
typename transpilation::ToTranspilationType<std::remove_cvref_t<U>>::Type;
return transpilation::Operation<transpilation::Operator::round, Type1, Type2>{
.operand1 = transpilation::to_transpilation_type(_t),
.operand2 = transpilation::to_transpilation_type(_u)};
}
template <class T>
auto round(const T& _t) {
return round(_t, 0);
}
template <class T, class U>
auto rtrim(const T& _t, const U& _u) {
using Type1 =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
using Type2 =
typename transpilation::ToTranspilationType<std::remove_cvref_t<U>>::Type;
return transpilation::Operation<transpilation::Operator::rtrim, Type1, Type2>{
.operand1 = transpilation::to_transpilation_type(_t),
.operand2 = transpilation::to_transpilation_type(_u)};
}
template <class T>
auto rtrim(const T& _t) {
return rtrim(_t, std::string(" "));
}
template <class T>
auto sin(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::sin, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T>
auto sqrt(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::sqrt, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T>
auto tan(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::tan, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
template <class T, class U>
auto trim(const T& _t, const U& _u) {
using Type1 =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
using Type2 =
typename transpilation::ToTranspilationType<std::remove_cvref_t<U>>::Type;
return transpilation::Operation<transpilation::Operator::trim, Type1, Type2>{
.operand1 = transpilation::to_transpilation_type(_t),
.operand2 = transpilation::to_transpilation_type(_u)};
}
template <class T>
auto trim(const T& _t) {
return trim(_t, std::string(" "));
}
template <class T>
auto upper(const T& _t) {
using Type =
typename transpilation::ToTranspilationType<std::remove_cvref_t<T>>::Type;
return transpilation::Operation<transpilation::Operator::upper, Type>{
.operand1 = transpilation::to_transpilation_type(_t)};
}
} // namespace sqlgen
#endif

View File

@@ -0,0 +1,86 @@
#ifndef SQLGEN_TRANSPILATION_AGGREGATION_HPP_
#define SQLGEN_TRANSPILATION_AGGREGATION_HPP_
#include <string>
#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 <AggregationOp _agg, class _ValueType>
struct Aggregation {
static constexpr auto agg = _agg;
using ValueType = _ValueType;
template <rfl::internal::StringLiteral _new_name>
auto as() const noexcept {
using T = std::remove_cvref_t<decltype(*this)>;
return transpilation::As<T, _new_name>{.val = *this};
}
ValueType val;
bool distinct = false;
template <class T>
friend auto operator/(const Aggregation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::divides, Aggregation, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
template <class T>
friend auto operator-(const Aggregation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::minus, Aggregation, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
template <class T>
friend auto operator%(const Aggregation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::mod, Aggregation, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
template <class T>
friend auto operator*(const Aggregation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::multiplies, Aggregation, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
template <class T>
friend auto operator+(const Aggregation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::plus, Aggregation, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
};
template <AggregationOp _agg, class _ValueType>
struct ToTranspilationType<Aggregation<_agg, _ValueType>> {
using Type = Aggregation<_agg, _ValueType>;
Type operator()(const Type& _val) const noexcept { return _val; }
};
} // namespace sqlgen::transpilation
#endif

View File

@@ -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

View File

@@ -11,6 +11,22 @@ template <class _ConditionType>
struct Condition {
using ConditionType = _ConditionType;
ConditionType condition;
auto operator!() {
return make_condition(conditions::Not<_ConditionType>{.cond = condition});
}
template <class C2>
friend auto operator&&(const Condition& _cond1, const Condition<C2>& _cond2) {
return make_condition(conditions::And<_ConditionType, C2>{
.cond1 = _cond1.condition, .cond2 = _cond2.condition});
}
template <class C2>
friend auto operator||(const Condition& _cond1, const Condition<C2>& _cond2) {
return make_condition(conditions::Or<_ConditionType, C2>{
.cond1 = _cond1.condition, .cond2 = _cond2.condition});
}
};
template <class T>
@@ -18,18 +34,6 @@ auto make_condition(T&& _t) {
return Condition<std::remove_cvref_t<T>>{.condition = _t};
}
template <class C1, class C2>
auto operator&&(const Condition<C1>& _cond1, const Condition<C2>& _cond2) {
return make_condition(conditions::And<C1, C2>{.cond1 = _cond1.condition,
.cond2 = _cond2.condition});
}
template <class C1, class C2>
auto operator||(const Condition<C1>& _cond1, const Condition<C2>& _cond2) {
return make_condition(conditions::Or<C1, C2>{.cond1 = _cond1.condition,
.cond2 = _cond2.condition});
}
} // namespace sqlgen::transpilation
#endif

View File

@@ -0,0 +1,153 @@
#ifndef SQLGEN_TRANSPILATION_OPERATION_HPP_
#define SQLGEN_TRANSPILATION_OPERATION_HPP_
#include <string>
#include <type_traits>
#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 <class T>
struct TypeHolder {};
template <Operator _op, class _Operand1Type, class _Operand2Type = Nothing,
class _Operand3Type = Nothing>
struct Operation {
static constexpr Operator op = _op;
using Operand1Type = _Operand1Type;
using Operand2Type = _Operand2Type;
using Operand3Type = _Operand3Type;
Operand1Type operand1;
Operand2Type operand2;
Operand3Type operand3;
template <rfl::internal::StringLiteral _new_name>
auto as() const noexcept {
using T = std::remove_cvref_t<decltype(*this)>;
return transpilation::As<T, _new_name>{.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 <class T>
friend auto operator==(const Operation& _o, const T& _t) {
return make_condition(conditions::equal(_o, to_transpilation_type(_t)));
}
template <class T>
friend auto operator!=(const Operation& _o, const T& _t) {
return make_condition(conditions::not_equal(_o, to_transpilation_type(_t)));
}
template <class T>
friend auto operator<(const Operation& _o, const T& _t) {
return make_condition(
conditions::lesser_than(_o, to_transpilation_type(_t)));
}
template <class T>
friend auto operator<=(const Operation& _o, const T& _t) {
return make_condition(
conditions::lesser_equal(_o, to_transpilation_type(_t)));
}
template <class T>
friend auto operator>(const Operation& _o, const T& _t) {
return make_condition(
conditions::greater_than(_o, to_transpilation_type(_t)));
}
template <class T>
friend auto operator>=(const Operation& _o, const T& _t) {
return make_condition(
conditions::greater_equal(_o, to_transpilation_type(_t)));
}
template <class T>
friend auto operator/(const Operation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::divides,
Operation<_op, _Operand1Type, _Operand2Type>, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
template <class T>
friend auto operator-(const Operation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::minus,
Operation<_op, _Operand1Type, _Operand2Type>, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
template <class T>
friend auto operator%(const Operation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::mod,
Operation<_op, _Operand1Type, _Operand2Type>, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
template <class T>
friend auto operator*(const Operation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::multiplies,
Operation<_op, _Operand1Type, _Operand2Type>, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
template <class T>
friend auto operator+(const Operation& _op1, const T& _op2) noexcept {
using OtherType = typename transpilation::ToTranspilationType<
std::remove_cvref_t<T>>::Type;
return Operation<Operator::plus,
Operation<_op, _Operand1Type, _Operand2Type>, OtherType>{
.operand1 = _op1, .operand2 = to_transpilation_type(_op2)};
}
};
template <Operator _op, class _Operand1Type, class _Operand2Type>
struct ToTranspilationType<Operation<_op, _Operand1Type, _Operand2Type>> {
using Type = Operation<_op, _Operand1Type, _Operand2Type>;
Type operator()(const Type& _val) const noexcept { return _val; }
};
} // namespace sqlgen::transpilation
#endif

View File

@@ -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

View File

@@ -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

View File

@@ -1,81 +0,0 @@
#ifndef SQLGEN_TRANSPILATION_AGGREGATIONS_HPP_
#define SQLGEN_TRANSPILATION_AGGREGATIONS_HPP_
#include <string>
#include "As.hpp"
namespace sqlgen::transpilation::aggregations {
/// To be used when we want to count everything.
struct All {};
template <class _ValueType>
struct Avg {
using ValueType = _ValueType;
template <rfl::internal::StringLiteral _new_name>
auto as() const noexcept {
using T = std::remove_cvref_t<decltype(*this)>;
return transpilation::As<T, _new_name>{.val = *this};
}
ValueType val;
};
template <class _ValueType>
struct Count {
using ValueType = _ValueType;
template <rfl::internal::StringLiteral _new_name>
auto as() const noexcept {
using T = std::remove_cvref_t<decltype(*this)>;
return transpilation::As<T, _new_name>{.val = *this};
}
ValueType val;
bool distinct = false;
};
template <class _ValueType>
struct Max {
using ValueType = _ValueType;
template <rfl::internal::StringLiteral _new_name>
auto as() const noexcept {
using T = std::remove_cvref_t<decltype(*this)>;
return transpilation::As<T, _new_name>{.val = *this};
}
ValueType val;
};
template <class _ValueType>
struct Min {
using ValueType = _ValueType;
template <rfl::internal::StringLiteral _new_name>
auto as() const noexcept {
using T = std::remove_cvref_t<decltype(*this)>;
return transpilation::As<T, _new_name>{.val = *this};
}
ValueType val;
};
template <class _ValueType>
struct Sum {
using ValueType = _ValueType;
template <rfl::internal::StringLiteral _new_name>
auto as() const noexcept {
using T = std::remove_cvref_t<decltype(*this)>;
return transpilation::As<T, _new_name>{.val = *this};
}
ValueType val;
};
} // namespace sqlgen::transpilation::aggregations
#endif

View File

@@ -49,7 +49,8 @@ struct CheckAggregation<StructType, rfl::Tuple<FieldTypes...>,
static constexpr bool value =
(true && ... &&
(MakeField<StructType, FieldTypes>::is_aggregation ||
!MakeField<StructType, FieldTypes>::is_column ||
(!MakeField<StructType, FieldTypes>::is_column &&
!MakeField<StructType, FieldTypes>::is_operation) ||
included_in_group_by<FieldTypes>));
static_assert(value,

View File

@@ -126,6 +126,13 @@ struct NotEqual {
OpType2 op2;
};
template <class CondType>
struct Not {
using ResultType = bool;
CondType cond;
};
template <class OpType1, class OpType2>
auto not_equal(const OpType1& _op1, const OpType2& _op2) {
return NotEqual<std::remove_cvref_t<OpType1>, std::remove_cvref_t<OpType2>>{

View File

@@ -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 <AggregationOp agg>
struct DynamicAggregation;
template <>
struct DynamicAggregation<AggregationOp::avg> {
using Type = dynamic::Aggregation::Avg;
};
template <>
struct DynamicAggregation<AggregationOp::count> {
using Type = dynamic::Aggregation::Count;
};
template <>
struct DynamicAggregation<AggregationOp::max> {
using Type = dynamic::Aggregation::Max;
};
template <>
struct DynamicAggregation<AggregationOp::min> {
using Type = dynamic::Aggregation::Min;
};
template <>
struct DynamicAggregation<AggregationOp::sum> {
using Type = dynamic::Aggregation::Sum;
};
template <AggregationOp agg>
using dynamic_aggregation_t = typename DynamicAggregation<agg>::Type;
} // namespace sqlgen::transpilation
#endif

View File

@@ -0,0 +1,208 @@
#ifndef SQLGEN_TRANSPILATION_DYNAMICOPERATORT_HPP_
#define SQLGEN_TRANSPILATION_DYNAMICOPERATORT_HPP_
#include <limits>
#include "../dynamic/Operation.hpp"
#include "Operator.hpp"
#include "OperatorCategory.hpp"
namespace sqlgen::transpilation {
template <Operator op>
struct DynamicOperator;
template <>
struct DynamicOperator<Operator::abs> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Abs;
};
template <>
struct DynamicOperator<Operator::cast> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::other;
using Type = dynamic::Operation::Cast;
};
template <>
struct DynamicOperator<Operator::ceil> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Ceil;
};
template <>
struct DynamicOperator<Operator::coalesce> {
static constexpr size_t num_operands = std::numeric_limits<size_t>::max();
static constexpr auto category = OperatorCategory::other;
using Type = dynamic::Operation::Coalesce;
};
template <>
struct DynamicOperator<Operator::concat> {
static constexpr size_t num_operands = std::numeric_limits<size_t>::max();
static constexpr auto category = OperatorCategory::string;
using Type = dynamic::Operation::Concat;
};
template <>
struct DynamicOperator<Operator::cos> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Cos;
};
template <>
struct DynamicOperator<Operator::divides> {
static constexpr size_t num_operands = 2;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Divides;
};
template <>
struct DynamicOperator<Operator::exp> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Exp;
};
template <>
struct DynamicOperator<Operator::floor> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Floor;
};
template <>
struct DynamicOperator<Operator::length> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::string;
using Type = dynamic::Operation::Length;
};
template <>
struct DynamicOperator<Operator::ln> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Ln;
};
template <>
struct DynamicOperator<Operator::log2> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Log2;
};
template <>
struct DynamicOperator<Operator::lower> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::string;
using Type = dynamic::Operation::Lower;
};
template <>
struct DynamicOperator<Operator::ltrim> {
static constexpr size_t num_operands = 2;
static constexpr auto category = OperatorCategory::string;
using Type = dynamic::Operation::LTrim;
};
template <>
struct DynamicOperator<Operator::minus> {
static constexpr size_t num_operands = 2;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Minus;
};
template <>
struct DynamicOperator<Operator::mod> {
static constexpr size_t num_operands = 2;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Mod;
};
template <>
struct DynamicOperator<Operator::multiplies> {
static constexpr size_t num_operands = 2;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Multiplies;
};
template <>
struct DynamicOperator<Operator::plus> {
static constexpr size_t num_operands = 2;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Plus;
};
template <>
struct DynamicOperator<Operator::replace> {
static constexpr size_t num_operands = 3;
static constexpr auto category = OperatorCategory::other;
using Type = dynamic::Operation::Replace;
};
template <>
struct DynamicOperator<Operator::round> {
static constexpr size_t num_operands = 2;
static constexpr auto category = OperatorCategory::other;
using Type = dynamic::Operation::Round;
};
template <>
struct DynamicOperator<Operator::rtrim> {
static constexpr size_t num_operands = 2;
static constexpr auto category = OperatorCategory::string;
using Type = dynamic::Operation::RTrim;
};
template <>
struct DynamicOperator<Operator::sin> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Sin;
};
template <>
struct DynamicOperator<Operator::sqrt> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Sqrt;
};
template <>
struct DynamicOperator<Operator::tan> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::numerical;
using Type = dynamic::Operation::Tan;
};
template <>
struct DynamicOperator<Operator::trim> {
static constexpr size_t num_operands = 2;
static constexpr auto category = OperatorCategory::string;
using Type = dynamic::Operation::Trim;
};
template <>
struct DynamicOperator<Operator::upper> {
static constexpr size_t num_operands = 1;
static constexpr auto category = OperatorCategory::string;
using Type = dynamic::Operation::Upper;
};
template <Operator op>
using dynamic_operator_t = typename DynamicOperator<op>::Type;
template <Operator op>
inline constexpr size_t num_operands_v = DynamicOperator<op>::num_operands;
template <Operator op>
inline constexpr auto operator_category_v = DynamicOperator<op>::category;
} // namespace sqlgen::transpilation
#endif

View File

@@ -0,0 +1,64 @@
#ifndef SQLGEN_TRANSPILATION_FLATTEN_FIELDS_T_HPP_
#define SQLGEN_TRANSPILATION_FLATTEN_FIELDS_T_HPP_
#include <rfl.hpp>
#include <type_traits>
#include "As.hpp"
#include "make_field.hpp"
#include "remove_as_t.hpp"
namespace sqlgen::transpilation {
template <class TupleType>
struct TupleWrapper;
template <class... FieldTypes>
struct TupleWrapper<rfl::Tuple<FieldTypes...>> {
using Type = rfl::Tuple<FieldTypes...>;
template <class... OtherFieldTypes>
friend consteval auto operator+(
const TupleWrapper&,
const TupleWrapper<rfl::Tuple<OtherFieldTypes...>>&) {
return TupleWrapper<rfl::Tuple<FieldTypes..., OtherFieldTypes...>>{};
}
};
template <class StructType, class FieldsType>
struct FlattenFields;
template <class StructType, class FieldType>
struct ExtractFields;
template <class StructType, class FieldType>
requires(!MakeField<StructType, remove_as_t<FieldType>>::is_operation)
struct ExtractFields<StructType, FieldType> {
using Type = rfl::Tuple<remove_as_t<FieldType>>;
};
template <class StructType, class FieldType>
requires(MakeField<StructType, remove_as_t<FieldType>>::is_operation)
struct ExtractFields<StructType, FieldType> {
using Type = typename FlattenFields<
StructType,
typename MakeField<StructType, remove_as_t<FieldType>>::Operands>::Type;
};
template <class StructType, class... FieldTypes>
struct FlattenFields<StructType, rfl::Tuple<FieldTypes...>> {
static constexpr auto wrapper =
(TupleWrapper<rfl::Tuple<>>{} + ... +
TupleWrapper<typename ExtractFields<StructType, FieldTypes>::Type>{});
using Type = typename decltype(wrapper)::Type;
};
template <class StructType, class FieldsType>
using flatten_fields_t =
typename FlattenFields<std::remove_cvref_t<StructType>,
std::remove_cvref_t<FieldsType>>::Type;
} // namespace sqlgen::transpilation
#endif

View File

@@ -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 <class StructType, class FieldType>
struct MakeField;
template <class StructType, class ValueType>
template <class StructType, class T>
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<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<StructType, Col<_name>> {
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 <class StructType, class T>
struct MakeField<StructType, Value<T>> {
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<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<StructType, As<ValueType, _new_name>> {
static constexpr bool is_aggregation =
MakeField<StructType, ValueType>::is_aggregation;
static constexpr bool is_column = MakeField<StructType, ValueType>::is_column;
static constexpr bool is_operation =
MakeField<StructType, ValueType>::is_operation;
using Name = Literal<_new_name>;
using Type =
@@ -64,132 +91,286 @@ struct MakeField<StructType, As<ValueType, _new_name>> {
dynamic::SelectFrom::Field operator()(const auto& _as) const {
return dynamic::SelectFrom::Field{
.val = MakeField<StructType, std::remove_cvref_t<ValueType>>{}(_as.val)
.val,
.val =
dynamic::Operation{
.val = MakeField<StructType, std::remove_cvref_t<ValueType>>{}(
_as.val)
.val.val},
.as = _new_name.str()};
}
};
template <class StructType, rfl::internal::StringLiteral _name>
struct MakeField<StructType, aggregations::Avg<Col<_name>>> {
static_assert(all_columns_exist<StructType, Col<_name>>(),
"A column required in the AVG aggregation does not exist.");
static_assert(
std::is_integral_v<
remove_nullable_t<underlying_t<StructType, Col<_name>>>> ||
std::is_floating_point_v<
remove_nullable_t<underlying_t<StructType, Col<_name>>>>,
"Values inside the AVG aggregation must be numerical.");
template <class StructType, AggregationOp _agg, class ValueType>
struct MakeField<StructType, Aggregation<_agg, ValueType>> {
static_assert(std::is_integral_v<
remove_nullable_t<underlying_t<StructType, ValueType>>> ||
std::is_floating_point_v<
remove_nullable_t<underlying_t<StructType, ValueType>>>,
"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<StructType, std::remove_cvref_t<ValueType>>::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<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<ValueType>>{}(
_val.val)
.val)}}}};
}
};
template <class StructType, rfl::internal::StringLiteral _name>
struct MakeField<StructType, aggregations::Count<Col<_name>>> {
struct MakeField<StructType, Aggregation<AggregationOp::count, Col<_name>>> {
static_assert(all_columns_exist<StructType, Col<_name>>(),
"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 <class StructType>
struct MakeField<StructType, aggregations::Count<aggregations::All>> {
struct MakeField<StructType, Aggregation<AggregationOp::count, All>> {
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 <class StructType, rfl::internal::StringLiteral _name>
struct MakeField<StructType, aggregations::Max<Col<_name>>> {
static_assert(all_columns_exist<StructType, Col<_name>>(),
"A column required in the MAX aggregation does not exist.");
template <class StructType, class Operand1Type, class TargetType>
struct MakeField<StructType, Operation<Operator::cast, Operand1Type,
TypeHolder<TargetType>>> {
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<TargetType>;
using Operands = rfl::Tuple<Operand1Type>;
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<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand1Type>>{}(
_o.operand1)
.val),
.target_type =
parsing::Parser<std::remove_cvref_t<TargetType>>::to_type()}}};
}
};
template <class StructType, rfl::internal::StringLiteral _name>
struct MakeField<StructType, aggregations::Min<Col<_name>>> {
static_assert(all_columns_exist<StructType, Col<_name>>(),
"A column required in MIN aggregation does not exist.");
template <class StructType, Operator _op, class... OperandTypes>
requires((_op == Operator::coalesce) || (_op == Operator::concat))
struct MakeField<StructType, Operation<_op, rfl::Tuple<OperandTypes...>>> {
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<StructType, Operation<_op, rfl::Tuple<OperandTypes...>>>;
using Operands = rfl::Tuple<OperandTypes...>;
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<dynamic::Operation>>(
{Ref<dynamic::Operation>::make(
MakeField<StructType,
std::remove_cvref_t<OperandTypes>>{}(_ops)
.val)...});
},
_o.operand1)}}};
}
};
template <class StructType, rfl::internal::StringLiteral _name>
struct MakeField<StructType, aggregations::Sum<Col<_name>>> {
static_assert(all_columns_exist<StructType, Col<_name>>(),
"A column required in SUM aggregation does not exist.");
template <class StructType, class Operand1Type, class Operand2Type,
class Operand3Type>
struct MakeField<StructType, Operation<Operator::replace, Operand1Type,
Operand2Type, Operand3Type>> {
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<underlying_t<StructType, Col<_name>>>> ||
std::is_floating_point_v<
remove_nullable_t<underlying_t<StructType, Col<_name>>>>,
"Values inside the SUM aggregation must be numerical.");
using Name = Nothing;
using Type =
underlying_t<StructType, Operation<Operator::replace, Operand1Type,
Operand2Type, Operand3Type>>;
using Operands = rfl::Tuple<Operand1Type, Operand2Type, Operand3Type>;
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<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand1Type>>{}(
_o.operand1)
.val),
.op2 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand2Type>>{}(
_o.operand2)
.val),
.op3 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand3Type>>{}(
_o.operand3)
.val)}}};
}
};
template <class StructType, class Operand1Type, class Operand2Type>
struct MakeField<StructType,
Operation<Operator::round, Operand1Type, Operand2Type>> {
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<StructType,
Operation<Operator::round, Operand1Type, Operand2Type>>;
using Operands = rfl::Tuple<Operand1Type, Operand2Type>;
dynamic::SelectFrom::Field operator()(const auto& _o) const {
return dynamic::SelectFrom::Field{
dynamic::Operation{dynamic::Operation::Round{
.op1 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand1Type>>{}(
_o.operand1)
.val),
.op2 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand2Type>>{}(
_o.operand2)
.val)}}};
}
};
template <class StructType, Operator _op, class Operand1Type>
requires((num_operands_v<_op>) == 1 &&
(operator_category_v<_op>) == OperatorCategory::string)
struct MakeField<StructType, Operation<_op, Operand1Type>> {
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<StructType, Operation<_op, Operand1Type>>;
using Operands = rfl::Tuple<Operand1Type>;
dynamic::SelectFrom::Field operator()(const auto& _o) const {
using DynamicOperatorType = dynamic_operator_t<_op>;
return dynamic::SelectFrom::Field{dynamic::Operation{DynamicOperatorType{
.op1 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand1Type>>{}(
_o.operand1)
.val)}}};
}
};
template <class StructType, Operator _op, class Operand1Type,
class Operand2Type>
requires((num_operands_v<_op>) == 2 &&
(operator_category_v<_op>) == OperatorCategory::string)
struct MakeField<StructType, Operation<_op, Operand1Type, Operand2Type>> {
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<StructType, Operation<_op, Operand1Type, Operand2Type>>;
using Operands = rfl::Tuple<Operand1Type, Operand2Type>;
dynamic::SelectFrom::Field operator()(const auto& _o) const {
using DynamicOperatorType = dynamic_operator_t<_op>;
return dynamic::SelectFrom::Field{dynamic::Operation{DynamicOperatorType{
.op1 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand1Type>>{}(
_o.operand1)
.val),
.op2 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand2Type>>{}(
_o.operand2)
.val)}}};
}
};
template <class StructType, Operator _op, class Operand1Type>
requires((num_operands_v<_op>) == 1 &&
(operator_category_v<_op>) == OperatorCategory::numerical)
struct MakeField<StructType, Operation<_op, Operand1Type>> {
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<StructType, Operation<_op, Operand1Type>>;
using Operands = rfl::Tuple<Operand1Type>;
dynamic::SelectFrom::Field operator()(const auto& _o) const {
using DynamicOperatorType = dynamic_operator_t<_op>;
return dynamic::SelectFrom::Field{dynamic::Operation{DynamicOperatorType{
.op1 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand1Type>>{}(
_o.operand1)
.val)}}};
}
};
template <class StructType, Operator _op, class Operand1Type,
class Operand2Type>
requires((num_operands_v<_op>) == 2 &&
(operator_category_v<_op>) == OperatorCategory::numerical)
struct MakeField<StructType, Operation<_op, Operand1Type, Operand2Type>> {
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<StructType, Operation<_op, Operand1Type, Operand2Type>>;
using Operands = rfl::Tuple<Operand1Type, Operand2Type>;
dynamic::SelectFrom::Field operator()(const auto& _o) const {
using DynamicOperatorType = dynamic_operator_t<_op>;
return dynamic::SelectFrom::Field{dynamic::Operation{DynamicOperatorType{
.op1 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand1Type>>{}(
_o.operand1)
.val),
.op2 = Ref<dynamic::Operation>::make(
MakeField<StructType, std::remove_cvref_t<Operand2Type>>{}(
_o.operand2)
.val)}}};
}
};

View File

@@ -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<T, conditions::And<CondType1, CondType2>> {
}
};
template <class T, rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
struct ToCondition<T, conditions::Equal<Col<_name1>, Col<_name2>>> {
static_assert(all_columns_exist<T, Col<_name1>, Col<_name2>>(),
"All columns must exist.");
static_assert(std::equality_comparable_with<underlying_t<T, Col<_name1>>,
underlying_t<T, Col<_name2>>>,
template <class T, class Op1Type, class Op2Type>
struct ToCondition<T, conditions::Equal<Op1Type, Op2Type>> {
static_assert(std::equality_comparable_with<underlying_t<T, Op1Type>,
underlying_t<T, Op2Type>>,
"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<T>(_cond.op1).val,
.op2 = make_field<T>(_cond.op2).val}};
}
};
template <class T, rfl::internal::StringLiteral _name, class V>
struct ToCondition<T, conditions::Equal<Col<_name>, Value<V>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
static_assert(std::equality_comparable_with<underlying_t<T, Col<_name>>,
underlying_t<T, Value<V>>>,
"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 <class T, rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
struct ToCondition<T, conditions::GreaterEqual<Col<_name1>, Col<_name2>>> {
static_assert(all_columns_exist<T, Col<_name1>, Col<_name2>>(),
"All columns must exist.");
static_assert(std::totally_ordered_with<underlying_t<T, Col<_name1>>,
underlying_t<T, Col<_name2>>>,
template <class T, class Op1Type, class Op2Type>
struct ToCondition<T, conditions::GreaterEqual<Op1Type, Op2Type>> {
static_assert(std::totally_ordered_with<underlying_t<T, Op1Type>,
underlying_t<T, Op2Type>>,
"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<T>(_cond.op1).val,
.op2 = make_field<T>(_cond.op2).val}};
}
};
template <class T, rfl::internal::StringLiteral _name, class V>
struct ToCondition<T, conditions::GreaterEqual<Col<_name>, Value<V>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
static_assert(std::totally_ordered_with<underlying_t<T, Col<_name>>,
underlying_t<T, Value<V>>>,
"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 <class T, rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
struct ToCondition<T, conditions::GreaterThan<Col<_name1>, Col<_name2>>> {
static_assert(all_columns_exist<T, Col<_name1>, Col<_name2>>(),
"All columns must exist.");
static_assert(std::totally_ordered_with<underlying_t<T, Col<_name1>>,
underlying_t<T, Col<_name2>>>,
template <class T, class Op1Type, class Op2Type>
struct ToCondition<T, conditions::GreaterThan<Op1Type, Op2Type>> {
static_assert(std::totally_ordered_with<underlying_t<T, Op1Type>,
underlying_t<T, Op2Type>>,
"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<T>(_cond.op1).val,
.op2 = make_field<T>(_cond.op2).val}};
}
};
template <class T, rfl::internal::StringLiteral _name, class V>
struct ToCondition<T, conditions::GreaterThan<Col<_name>, Value<V>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
static_assert(std::totally_ordered_with<underlying_t<T, Col<_name>>,
underlying_t<T, Value<V>>>,
"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 <class T, rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
struct ToCondition<T, conditions::LesserEqual<Col<_name1>, Col<_name2>>> {
static_assert(all_columns_exist<T, Col<_name1>, Col<_name2>>(),
"All columns must exist.");
static_assert(std::totally_ordered_with<underlying_t<T, Col<_name1>>,
underlying_t<T, Col<_name2>>>,
template <class T, class Op1Type, class Op2Type>
struct ToCondition<T, conditions::LesserEqual<Op1Type, Op2Type>> {
static_assert(std::totally_ordered_with<underlying_t<T, Op1Type>,
underlying_t<T, Op2Type>>,
"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<T>(_cond.op1).val,
.op2 = make_field<T>(_cond.op2).val}};
}
};
template <class T, rfl::internal::StringLiteral _name, class V>
struct ToCondition<T, conditions::LesserEqual<Col<_name>, Value<V>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
static_assert(std::totally_ordered_with<underlying_t<T, Col<_name>>,
underlying_t<T, Value<V>>>,
"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 <class T, rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
struct ToCondition<T, conditions::LesserThan<Col<_name1>, Col<_name2>>> {
static_assert(all_columns_exist<T, Col<_name1>, Col<_name2>>(),
"All columns must exist.");
static_assert(std::totally_ordered_with<underlying_t<T, Col<_name1>>,
underlying_t<T, Col<_name2>>>,
template <class T, class Op1Type, class Op2Type>
struct ToCondition<T, conditions::LesserThan<Op1Type, Op2Type>> {
static_assert(std::totally_ordered_with<underlying_t<T, Op1Type>,
underlying_t<T, Op2Type>>,
"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<T>(_cond.op1).val,
.op2 = make_field<T>(_cond.op2).val}};
}
};
template <class T, rfl::internal::StringLiteral _name, class V>
struct ToCondition<T, conditions::LesserThan<Col<_name>, Value<V>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
static_assert(std::totally_ordered_with<underlying_t<T, Col<_name>>,
underlying_t<T, Value<V>>>,
"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 <class T, rfl::internal::StringLiteral _name>
struct ToCondition<T, conditions::Like<Col<_name>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
template <class T, class OpType>
struct ToCondition<T, conditions::Like<OpType>> {
static_assert(
std::equality_comparable_with<underlying_t<T, Col<_name>>,
std::equality_comparable_with<underlying_t<T, OpType>,
underlying_t<T, Value<std::string>>>,
"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<T>(_cond.op).val,
.pattern = to_value(_cond.pattern)}};
}
};
template <class T, rfl::internal::StringLiteral _name>
struct ToCondition<T, conditions::IsNotNull<Col<_name>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
dynamic::Condition operator()(const auto&) const {
template <class T, class OpType>
struct ToCondition<T, conditions::IsNotNull<OpType>> {
dynamic::Condition operator()(const auto& _cond) const {
return dynamic::Condition{.val = dynamic::Condition::IsNotNull{
.op = dynamic::Column{.name = _name.str()}}};
.op = make_field<T>(_cond.op).val}};
}
};
template <class T, rfl::internal::StringLiteral _name>
struct ToCondition<T, conditions::IsNull<Col<_name>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
dynamic::Condition operator()(const auto&) const {
return dynamic::Condition{.val = dynamic::Condition::IsNull{
.op = dynamic::Column{.name = _name.str()}}};
template <class T, class OpType>
struct ToCondition<T, conditions::IsNull<OpType>> {
dynamic::Condition operator()(const auto& _cond) const {
return dynamic::Condition{
.val = dynamic::Condition::IsNull{.op = make_field<T>(_cond.op).val}};
}
};
template <class T, rfl::internal::StringLiteral _name1,
rfl::internal::StringLiteral _name2>
struct ToCondition<T, conditions::NotEqual<Col<_name1>, Col<_name2>>> {
static_assert(all_columns_exist<T, Col<_name1>, Col<_name2>>(),
"All columns must exist.");
static_assert(std::equality_comparable_with<underlying_t<T, Col<_name1>>,
underlying_t<T, Col<_name2>>>,
template <class T, class CondType>
struct ToCondition<T, conditions::Not<CondType>> {
dynamic::Condition operator()(const auto& _cond) const {
return dynamic::Condition{
.val = dynamic::Condition::Not{
.cond = Ref<dynamic::Condition>::make(
ToCondition<T, std::remove_cvref_t<CondType>>{}(_cond.cond))}};
}
};
template <class T, class Op1Type, class Op2Type>
struct ToCondition<T, conditions::NotEqual<Op1Type, Op2Type>> {
static_assert(std::equality_comparable_with<underlying_t<T, Op1Type>,
underlying_t<T, Op2Type>>,
"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<T>(_cond.op1).val,
.op2 = make_field<T>(_cond.op2).val}};
}
};
template <class T, rfl::internal::StringLiteral _name, class V>
struct ToCondition<T, conditions::NotEqual<Col<_name>, Value<V>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
static_assert(std::equality_comparable_with<underlying_t<T, Col<_name>>,
underlying_t<T, Value<V>>>,
"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 <class T, rfl::internal::StringLiteral _name>
struct ToCondition<T, conditions::NotLike<Col<_name>>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
template <class T, class OpType>
struct ToCondition<T, conditions::NotLike<OpType>> {
static_assert(
std::equality_comparable_with<underlying_t<T, Col<_name>>,
std::equality_comparable_with<underlying_t<T, OpType>,
underlying_t<T, Value<std::string>>>,
"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<T>(_cond.op).val,
.pattern = to_value(_cond.pattern)}};
}
};

View File

@@ -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 <class StructType, class FieldsType, class WhereType,
dynamic::SelectFrom to_select_from(const FieldsType& _fields,
const WhereType& _where,
const LimitType& _limit) {
static_assert(check_aggregations<StructType, FieldsType, GroupByType>(),
"The aggregations were not set up correctly. Please check the "
"trace for a more detailed error message.");
static_assert(
check_aggregations<StructType, flatten_fields_t<StructType, FieldsType>,
GroupByType>(),
"The aggregations were not set up correctly. Please check the "
"trace for a more detailed error message.");
const auto fields = make_fields<StructType, FieldsType>(
_fields,

View File

@@ -77,9 +77,9 @@ struct ToSQL<Read<ContainerType, WhereType, OrderByType, LimitType>> {
};
template <class StructType, class FieldsType, class WhereType,
class GroupByType, class OrderByType, class LimitType>
class GroupByType, class OrderByType, class LimitType, class ToType>
struct ToSQL<SelectFrom<StructType, FieldsType, WhereType, GroupByType,
OrderByType, LimitType>> {
OrderByType, LimitType, ToType>> {
dynamic::Statement operator()(const auto& _select_from) const {
return to_select_from<StructType, FieldsType, WhereType, GroupByType,
OrderByType, LimitType>(

View File

@@ -0,0 +1,46 @@
#ifndef SQLGEN_TRANSPILATION_TO_TRANSPILATION_TYPE_HPP_
#define SQLGEN_TRANSPILATION_TO_TRANSPILATION_TYPE_HPP_
#include <string>
#include <type_traits>
#include "Value.hpp"
namespace sqlgen::transpilation {
template <class T>
struct ToTranspilationType;
template <class T>
struct ToTranspilationType {
using Type = Value<T>;
Type operator()(const T& _val) const noexcept { return make_value(_val); }
};
template <>
struct ToTranspilationType<const char*> {
using Type = Value<std::string>;
Type operator()(const char* _val) const noexcept { return make_value(_val); }
};
template <int _length>
struct ToTranspilationType<char[_length]> {
using Type = Value<std::string>;
Type operator()(const char* _val) const noexcept { return make_value(_val); }
};
template <class T>
auto to_transpilation_type(const T& _t) {
return ToTranspilationType<std::remove_cvref_t<T>>{}(_t);
}
inline auto to_transpilation_type(const char* _t) {
return ToTranspilationType<const char*>{}(_t);
}
} // namespace sqlgen::transpilation
#endif

View File

@@ -14,13 +14,13 @@ template <class T>
dynamic::Value to_value(const T& _t) {
using Type = std::remove_cvref_t<T>;
if constexpr (std::is_floating_point_v<Type>) {
return dynamic::Float{.val = static_cast<double>(_t)};
return dynamic::Value{dynamic::Float{.val = static_cast<double>(_t)}};
} else if constexpr (std::is_integral_v<Type>) {
return dynamic::Integer{.val = static_cast<int64_t>(_t)};
return dynamic::Value{dynamic::Integer{.val = static_cast<int64_t>(_t)}};
} else if constexpr (std::is_convertible_v<Type, std::string>) {
return dynamic::String{.val = std::string(_t)};
return dynamic::Value{dynamic::String{.val = std::string(_t)}};
} else if constexpr (has_reflection_method<Type>) {
return to_value(_t.reflection());

View File

@@ -2,11 +2,19 @@
#define SQLGEN_TRANSPILATION_UNDERLYINGT_HPP_
#include <rfl.hpp>
#include <string>
#include <type_traits>
#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 <class T, class _Type>
struct Underlying;
template <class T, AggregationOp _op, class ValueType>
struct Underlying<T, Aggregation<_op, ValueType>> {
using Type = typename Underlying<T, std::remove_cvref_t<ValueType>>::Type;
};
template <class T, rfl::internal::StringLiteral _name>
struct Underlying<T, Col<_name>> {
static_assert(all_columns_exist<T, Col<_name>>(), "All columns must exist.");
using Type = remove_reflection_t<rfl::field_type_t<_name, T>>;
};
@@ -24,6 +38,162 @@ struct Underlying<T, Desc<Col<_name>>> {
using Type = remove_reflection_t<rfl::field_type_t<_name, T>>;
};
template <class T, class Operand1Type, class TargetType>
struct Underlying<
T, Operation<Operator::cast, Operand1Type, TypeHolder<TargetType>>> {
using Type =
std::conditional_t<is_nullable_v<typename Underlying<
T, std::remove_cvref_t<Operand1Type>>::Type>,
std::optional<std::remove_cvref_t<TargetType>>,
std::remove_cvref_t<TargetType>>;
};
template <class T, class Head, class... Tail>
struct Underlying<T, Operation<Operator::coalesce, rfl::Tuple<Head, Tail...>>> {
using Operand1Type = typename Underlying<T, std::remove_cvref_t<Head>>::Type;
static_assert((true && ... &&
std::is_same_v<remove_nullable_t<Operand1Type>,
remove_nullable_t<typename Underlying<
T, std::remove_cvref_t<Tail>>::Type>>),
"All inputs into coalesce(...) must have the same type.");
using Type = std::conditional_t<
(is_nullable_v<Operand1Type> && ... &&
is_nullable_v<typename Underlying<T, std::remove_cvref_t<Tail>>::Type>),
std::optional<remove_nullable_t<Operand1Type>>,
remove_nullable_t<Operand1Type>>;
};
template <class T, class... OperandTypes>
struct Underlying<T, Operation<Operator::concat, rfl::Tuple<OperandTypes...>>> {
static_assert(
(true && ... &&
std::is_same_v<remove_nullable_t<typename Underlying<
T, std::remove_cvref_t<OperandTypes>>::Type>,
std::string>),
"Must be a string");
using Type =
std::conditional_t<(false || ... ||
is_nullable_v<typename Underlying<
T, std::remove_cvref_t<OperandTypes>>::Type>),
std::optional<std::string>, std::string>;
};
template <class T, class Operand1Type, class Operand2Type, class Operand3Type>
struct Underlying<
T, Operation<Operator::replace, Operand1Type, Operand2Type, Operand3Type>> {
using Underlying1 =
typename Underlying<T, std::remove_cvref_t<Operand1Type>>::Type;
using Underlying2 =
typename Underlying<T, std::remove_cvref_t<Operand2Type>>::Type;
using Underlying3 =
typename Underlying<T, std::remove_cvref_t<Operand3Type>>::Type;
static_assert(std::is_same_v<remove_nullable_t<Underlying1>, std::string>,
"Must be a string");
static_assert(std::is_same_v<remove_nullable_t<Underlying2>, std::string>,
"Must be a string");
static_assert(std::is_same_v<remove_nullable_t<Underlying3>, std::string>,
"Must be a string");
using Type = std::conditional_t<is_nullable_v<Underlying1> ||
is_nullable_v<Underlying2> ||
is_nullable_v<Underlying3>,
std::optional<std::string>, std::string>;
};
template <class T, class Operand1Type, class Operand2Type>
struct Underlying<T, Operation<Operator::round, Operand1Type, Operand2Type>> {
using Underlying1 =
typename Underlying<T, std::remove_cvref_t<Operand1Type>>::Type;
using Underlying2 =
typename Underlying<T, std::remove_cvref_t<Operand2Type>>::Type;
static_assert(std::is_integral_v<remove_nullable_t<Underlying1>> ||
std::is_floating_point_v<remove_nullable_t<Underlying1>>,
"Must be a numerical type");
static_assert(std::is_integral_v<Underlying2>, "Must be an integral type");
using Type = Underlying1;
};
template <class T, Operator _op, class Operand1Type>
requires((num_operands_v<_op>) == 1 &&
(operator_category_v<_op>) == OperatorCategory::string)
struct Underlying<T, Operation<_op, Operand1Type>> {
using Underlying1 =
typename Underlying<T, std::remove_cvref_t<Operand1Type>>::Type;
static_assert(std::is_same_v<remove_nullable_t<Underlying1>, std::string>,
"Must be a string");
using StringType =
std::conditional_t<is_nullable_v<Underlying1>, std::optional<std::string>,
std::string>;
using SizeType = std::conditional_t<is_nullable_v<Underlying1>,
std::optional<size_t>, size_t>;
using Type =
std::conditional_t<_op == Operator::length, SizeType, StringType>;
};
template <class T, Operator _op, class Operand1Type, class Operand2Type>
requires((num_operands_v<_op>) == 2 &&
(operator_category_v<_op>) == OperatorCategory::string)
struct Underlying<T, Operation<_op, Operand1Type, Operand2Type>> {
using Underlying1 =
typename Underlying<T, std::remove_cvref_t<Operand1Type>>::Type;
using Underlying2 =
typename Underlying<T, std::remove_cvref_t<Operand2Type>>::Type;
static_assert(std::is_same_v<remove_nullable_t<Underlying1>, std::string>,
"Must be a string");
static_assert(std::is_same_v<remove_nullable_t<Underlying2>, std::string>,
"Must be a string");
using Type = std::conditional_t<is_nullable_v<Underlying1> ||
is_nullable_v<Underlying2>,
std::optional<std::string>, std::string>;
};
template <class T, Operator _op, class Operand1Type>
requires((num_operands_v<_op>) == 1 &&
(operator_category_v<_op>) == OperatorCategory::numerical)
struct Underlying<T, Operation<_op, Operand1Type>> {
using Underlying1 =
typename Underlying<T, std::remove_cvref_t<Operand1Type>>::Type;
static_assert(std::is_integral_v<remove_nullable_t<Underlying1>> ||
std::is_floating_point_v<remove_nullable_t<Underlying1>>,
"Must be a numerical type");
using Type = Underlying1;
};
template <class T, Operator _op, class Operand1Type, class Operand2Type>
requires((num_operands_v<_op>) == 2 &&
(operator_category_v<_op>) == OperatorCategory::numerical)
struct Underlying<T, Operation<_op, Operand1Type, Operand2Type>> {
using Underlying1 =
typename Underlying<T, std::remove_cvref_t<Operand1Type>>::Type;
using Underlying2 =
typename Underlying<T, std::remove_cvref_t<Operand2Type>>::Type;
static_assert(
requires(remove_nullable_t<Underlying1> op1,
remove_nullable_t<Underlying2> 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<Underlying1>, remove_nullable_t<Underlying2>>;
using Type = std::conditional_t<is_nullable_v<Underlying1> ||
is_nullable_v<Underlying2>,
std::optional<ResultType>, ResultType>;
};
template <class T, class _Type>
struct Underlying<T, Value<_Type>> {
using Type = _Type;

View File

@@ -6,6 +6,7 @@
#include <stdexcept>
#include <type_traits>
#include "sqlgen/dynamic/Operation.hpp"
#include "sqlgen/internal/collect/vector.hpp"
#include "sqlgen/internal/strings/strings.hpp"
@@ -43,6 +44,8 @@ std::vector<std::string> 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<decltype(_agg)>;
std::stringstream stream;
if constexpr (std::is_same_v<Type, dynamic::Aggregation::Avg>) {
stream << "AVG(" << column_or_value_to_sql(_agg.val) << ")";
stream << "AVG(" << operation_to_sql(*_agg.val) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Count>) {
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<Type, dynamic::Aggregation::Max>) {
stream << "MAX(" << column_or_value_to_sql(_agg.val) << ")";
stream << "MAX(" << operation_to_sql(*_agg.val) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Min>) {
stream << "MIN(" << column_or_value_to_sql(_agg.val) << ")";
stream << "MIN(" << operation_to_sql(*_agg.val) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Sum>) {
stream << "SUM(" << column_or_value_to_sql(_agg.val) << ")";
stream << "SUM(" << operation_to_sql(*_agg.val) << ")";
} else {
static_assert(rfl::always_false_v<Type>, "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<Type, dynamic::Column>) {
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 <class ConditionType>
std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
using C = std::remove_cvref_t<ConditionType>;
std::stringstream stream;
if constexpr (std::is_same_v<C, dynamic::Condition::And>) {
@@ -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<C, dynamic::Condition::Equal>) {
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<C, dynamic::Condition::GreaterEqual>) {
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<C, dynamic::Condition::GreaterThan>) {
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<C, dynamic::Condition::IsNull>) {
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<C, dynamic::Condition::IsNotNull>) {
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<C, dynamic::Condition::LesserEqual>) {
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<C, dynamic::Condition::LesserThan>) {
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<C, dynamic::Condition::Like>) {
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<C, dynamic::Condition::Not>) {
stream << "NOT (" << condition_to_sql(*_condition.cond) << ")";
} else if constexpr (std::is_same_v<C, dynamic::Condition::NotEqual>) {
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<C, dynamic::Condition::NotLike>) {
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<C, dynamic::Condition::Or>) {
@@ -180,7 +183,6 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
} else {
static_assert(rfl::always_false_v<C>, "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<decltype(_val)>;
if constexpr (std::is_same_v<Type, dynamic::Aggregation>) {
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<decltype(_s)>;
std::stringstream stream;
if constexpr (std::is_same_v<Type, dynamic::Operation::Abs>) {
stream << "abs(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation>) {
stream << aggregation_to_sql(_s);
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Cast>) {
stream << "cast(" << operation_to_sql(*_s.op1) << " as "
<< type_to_sql(_s.target_type) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Coalesce>) {
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<Type, dynamic::Operation::Ceil>) {
stream << "ceil(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Column>) {
stream << column_or_value_to_sql(_s);
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Concat>) {
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<Type, dynamic::Operation::Cos>) {
stream << "cos(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Divides>) {
stream << "(" << operation_to_sql(*_s.op1) << ") / ("
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Exp>) {
stream << "exp(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Floor>) {
stream << "floor(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Length>) {
stream << "length(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Ln>) {
stream << "ln(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Log2>) {
stream << "log(2.0, " << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Lower>) {
stream << "lower(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::LTrim>) {
stream << "ltrim(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Minus>) {
stream << "(" << operation_to_sql(*_s.op1) << ") - ("
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Mod>) {
stream << "mod(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Multiplies>) {
stream << "(" << operation_to_sql(*_s.op1) << ") * ("
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Plus>) {
stream << "(" << operation_to_sql(*_s.op1) << ") + ("
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Replace>) {
stream << "replace(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ", " << operation_to_sql(*_s.op3)
<< ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Round>) {
stream << "round(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::RTrim>) {
stream << "rtrim(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Sin>) {
stream << "sin(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Sqrt>) {
stream << "sqrt(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Tan>) {
stream << "tan(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Trim>) {
stream << "trim(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Upper>) {
stream << "upper(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Value>) {
stream << column_or_value_to_sql(_s);
} else {
static_assert(rfl::always_false_v<Type>, "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<T, dynamic::types::Int64> ||
std::is_same_v<T, dynamic::types::UInt64>) {
return "BIGINT";
} else if constexpr (std::is_same_v<T, dynamic::types::Float32>) {
return "REAL";
} else if constexpr (std::is_same_v<T, dynamic::types::Float64>) {
return "DOUBLE PRECISION";
} else if constexpr (std::is_same_v<T, dynamic::types::Float32> ||
std::is_same_v<T, dynamic::types::Float64>) {
return "NUMERIC";
} else if constexpr (std::is_same_v<T, dynamic::types::Text>) {
return "TEXT";
} else if constexpr (std::is_same_v<T, dynamic::types::VarChar>) {

View File

@@ -2,6 +2,7 @@
#include <rfl.hpp>
#include <sstream>
#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 <class InsertOrWrite>
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<decltype(_agg)>;
std::stringstream stream;
if constexpr (std::is_same_v<Type, dynamic::Aggregation::Avg>) {
stream << "AVG(" << column_or_value_to_sql(_agg.val) << ")";
stream << "AVG(" << operation_to_sql(*_agg.val) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Count>) {
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<Type, dynamic::Aggregation::Max>) {
stream << "MAX(" << column_or_value_to_sql(_agg.val) << ")";
stream << "MAX(" << operation_to_sql(*_agg.val) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Min>) {
stream << "MIN(" << column_or_value_to_sql(_agg.val) << ")";
stream << "MIN(" << operation_to_sql(*_agg.val) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Sum>) {
stream << "SUM(" << column_or_value_to_sql(_agg.val) << ")";
stream << "SUM(" << operation_to_sql(*_agg.val) << ")";
} else {
static_assert(rfl::always_false_v<Type>, "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<Type, dynamic::Column>) {
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 <class ConditionType>
std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
using C = std::remove_cvref_t<ConditionType>;
std::stringstream stream;
if constexpr (std::is_same_v<C, dynamic::Condition::And>) {
@@ -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<C, dynamic::Condition::Equal>) {
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<C, dynamic::Condition::GreaterEqual>) {
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<C, dynamic::Condition::GreaterThan>) {
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<C, dynamic::Condition::IsNull>) {
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<C, dynamic::Condition::IsNotNull>) {
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<C, dynamic::Condition::LesserEqual>) {
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<C, dynamic::Condition::LesserThan>) {
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<C, dynamic::Condition::Like>) {
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<C, dynamic::Condition::Not>) {
stream << "NOT (" << condition_to_sql(*_condition.cond) << ")";
} else if constexpr (std::is_same_v<C, dynamic::Condition::NotEqual>) {
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<C, dynamic::Condition::NotLike>) {
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<C, dynamic::Condition::Or>) {
@@ -164,7 +167,6 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
} else {
static_assert(rfl::always_false_v<C>, "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<decltype(_val)>;
if constexpr (std::is_same_v<Type, dynamic::Aggregation>) {
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<decltype(_s)>;
std::stringstream stream;
if constexpr (std::is_same_v<Type, dynamic::Operation::Abs>) {
stream << "abs(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation>) {
stream << aggregation_to_sql(_s);
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Cast>) {
stream << "cast(" << operation_to_sql(*_s.op1) << " as "
<< type_to_sql(_s.target_type) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Coalesce>) {
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<Type, dynamic::Operation::Ceil>) {
stream << "ceil(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Column>) {
stream << column_or_value_to_sql(_s);
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Concat>) {
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<Type, dynamic::Operation::Cos>) {
stream << "cos(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Divides>) {
stream << "(" << operation_to_sql(*_s.op1) << ") / ("
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Exp>) {
stream << "exp(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Floor>) {
stream << "floor(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Length>) {
stream << "length(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Ln>) {
stream << "ln(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Log2>) {
stream << "log2(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Lower>) {
stream << "lower(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::LTrim>) {
stream << "ltrim(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Minus>) {
stream << "(" << operation_to_sql(*_s.op1) << ") - ("
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Mod>) {
stream << "mod(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Multiplies>) {
stream << "(" << operation_to_sql(*_s.op1) << ") * ("
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Plus>) {
stream << "(" << operation_to_sql(*_s.op1) << ") + ("
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Replace>) {
stream << "replace(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ", " << operation_to_sql(*_s.op3)
<< ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Round>) {
stream << "round(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::RTrim>) {
stream << "rtrim(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Sin>) {
stream << "sin(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Sqrt>) {
stream << "sqrt(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Tan>) {
stream << "tan(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Trim>) {
stream << "trim(" << operation_to_sql(*_s.op1) << ", "
<< operation_to_sql(*_s.op2) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Upper>) {
stream << "upper(" << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Value>) {
stream << column_or_value_to_sql(_s);
} else {
static_assert(rfl::always_false_v<Type>, "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");

View File

@@ -0,0 +1,72 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_group_by_with_operations {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(postgres, test_group_by) {
static_assert(std::ranges::input_range<sqlgen::Range<Person>>,
"Must be an input range.");
const auto people1 = std::vector<Person>(
{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<Person>(
"last_name"_c, trim("last_name"_c).as<"last_name_trimmed">(),
max(cast<double>("age"_c) + 1.0).as<"max_age_plus_one">(),
(min(cast<double>("age"_c)) + 1.0).as<"min_age_plus_one">(),
round(avg(cast<double>("age"_c))).as<"avg_age">()) |
where("age"_c < 18) | group_by("last_name"_c) | to<std::vector<Children>>;
const auto children = postgres::connect(credentials)
.and_then(drop<Person> | 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

View File

@@ -0,0 +1,83 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_operations {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(postgres, test_operations) {
const auto people1 = std::vector<Person>(
{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<Person>(
("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<double>("age"_c)), 2) | as<"exp_age">,
round(sqrt(cast<double>("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<std::vector<Children>>;
const auto children = postgres::connect(credentials)
.and_then(drop<Person> | 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

View File

@@ -0,0 +1,69 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_operations_with_nullable {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::optional<std::string> last_name;
std::optional<int> age;
};
TEST(postgres, test_operations_with_nullable) {
const auto people1 = std::vector<Person>(
{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<int> id_plus_age;
std::optional<int> age_times_2;
std::optional<int> id_plus_2_minus_age;
std::optional<std::string> full_name;
std::string last_name_or_none;
};
const auto get_children =
select_from<Person>(
("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<std::vector<Children>>;
const auto children = postgres::connect(credentials)
.and_then(drop<Person> | 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

View File

@@ -41,7 +41,7 @@ TEST(postgres, test_where) {
sqlgen::write(conn, people1).value();
const auto query = sqlgen::read<std::vector<Person>> |
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();

View File

@@ -0,0 +1,57 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/postgres.hpp>
#include <vector>
namespace test_where_with_operations {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(postgres, test_where_with_operations) {
const auto people1 = std::vector<Person>(
{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<Person> | if_exists);
sqlgen::write(conn, people1).value();
const auto query = sqlgen::read<std::vector<Person>> |
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

View File

@@ -0,0 +1,62 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_group_by_with_operations {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(sqlite, test_group_by_with_operations) {
const auto people1 = std::vector<Person>(
{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<Person>(
"last_name"_c, trim("last_name"_c).as<"last_name_trimmed">(),
max(cast<double>("age"_c) + 1.0).as<"max_age_plus_one">(),
(min(cast<double>("age"_c)) + 1.0).as<"min_age_plus_one">(),
round(avg(cast<double>("age"_c))).as<"avg_age">()) |
where("age"_c < 18) | group_by("last_name"_c) | to<std::vector<Children>>;
const auto children = sqlite::connect()
.and_then(drop<Person> | 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

View File

@@ -0,0 +1,75 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_operations {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(sqlite, test_operations) {
const auto people1 = std::vector<Person>(
{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<Person>(
("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<double>("age"_c)), 2) | as<"exp_age">,
round(sqrt(cast<double>("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<std::vector<Children>>;
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

View File

@@ -0,0 +1,62 @@
#include <gtest/gtest.h>
#include <optional>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_operations_with_nullable {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::optional<std::string> last_name;
std::optional<int> age;
};
TEST(sqlite, test_operations_with_nullable) {
const auto people1 = std::vector<Person>(
{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<int> id_plus_age;
std::optional<int> age_times_2;
std::optional<int> id_plus_2_minus_age;
std::optional<std::string> full_name;
std::string last_name_or_none;
};
const auto get_children =
select_from<Person>(
("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<std::vector<Children>>;
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

View File

@@ -33,7 +33,7 @@ TEST(sqlite, test_where) {
using namespace sqlgen;
const auto query = sqlgen::read<std::vector<Person>> |
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();

View File

@@ -0,0 +1,48 @@
#include <gtest/gtest.h>
#include <optional>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_where_with_nullable_operations {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
std::optional<int> age;
};
TEST(sqlite, test_where_with_nullable_operations) {
const auto people1 = std::vector<Person>(
{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<std::vector<Person>> |
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

View File

@@ -0,0 +1,47 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/sqlite.hpp>
#include <vector>
namespace test_where_with_operations {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(sqlite, test_where_with_operations) {
const auto people1 = std::vector<Person>(
{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<std::vector<Person>> |
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

View File

@@ -20,7 +20,8 @@
"dependencies": [
{
"name": "sqlite3",
"version>=": "3.49.1"
"version>=": "3.49.1",
"features": ["math"]
}
]
},