Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ 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

## Other Operations

- [Mathematical Operations](mathematical_operations.md) - How to use mathematical functions in queries (e.g., abs, ceil, floor, exp, trigonometric functions, round).
- [String Operations](string_operations.md) - How to manipulate and transform strings in queries (e.g., length, lower, upper, trim, replace, concat).
- [Type Conversion Operations](type_conversion_operations.md) - How to convert between types safely in queries (e.g., cast int to double).
- [Null Handling Operations](null_handling_operations.md) - How to handle nullable values and propagate nullability correctly (e.g., with coalesce and nullability rules).
- [Timestamp and Date/Time Functions](timestamp_operations.md) - How to work with timestamps, dates, and times (e.g., extract parts, perform arithmetic, convert formats).

## Data Types and Validation

Expand Down
72 changes: 72 additions & 0 deletions docs/mathematical_operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Mathematical Operations

The `sqlgen` library provides a set of mathematical functions for use in queries. These functions are type-safe and map to the appropriate SQL operations for the target database.

## Motivating Example
Suppose you want to analyze the ages of children in a family, grouped by their last name, and you want to compute the average, minimum, and maximum age (with some mathematical operations applied):

```cpp
struct Children {
std::string last_name;
double avg_age;
double max_age_plus_one;
double min_age_plus_one;
};

const auto get_children =
select_from<Person>(
"last_name"_c,
round(avg(cast<double>("age"_c))).as<"avg_age">(),
max(cast<double>("age"_c) + 1.0).as<"max_age_plus_one">(),
(min(cast<double>("age"_c)) + 1.0).as<"min_age_plus_one">()
)
| where("age"_c < 18)
| group_by("last_name"_c)
| to<std::vector<Children>>;
```

This query groups people by last name, filters for those under 18, and computes the average age (rounded), the maximum age plus one, and the minimum age plus one.

## 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">
```
81 changes: 81 additions & 0 deletions docs/null_handling_operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Null Handling Operations

The `sqlgen` library provides functions for handling null values in a type-safe way. These functions allow you to work with nullable columns and propagate nullability correctly in your queries.

## 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.
Loading
Loading