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
51 changes: 51 additions & 0 deletions docs/col.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,57 @@ FROM "Person"
WHERE "first_name" NOT LIKE 'H%';
```

#### IN and NOT IN Operations

Use IN and NOT IN to check if a column value matches any value in a list:

```cpp
using namespace sqlgen;
using namespace sqlgen::literals;

// Find people with specific first names (variadic arguments)
const auto query1 = read<std::vector<Person>> |
where("first_name"_c.in("Bart", "Lisa", "Maggie"));

// Find people NOT with specific first names (variadic arguments)
const auto query2 = read<std::vector<Person>> |
where("first_name"_c.not_in("Homer", "Hugo"));

// Find people with specific first names (using vector)
const auto names = std::vector<std::string>({"Bart", "Lisa", "Maggie"});
const auto query3 = read<std::vector<Person>> |
where("first_name"_c.in(names));

// Find people NOT with specific first names (using vector)
const auto excluded_names = std::vector<std::string>({"Homer", "Hugo"});
const auto query4 = read<std::vector<Person>> |
where("first_name"_c.not_in(excluded_names));
```

This generates SQL like:

```sql
-- For query1
SELECT "id", "first_name", "last_name", "age"
FROM "Person"
WHERE "first_name" IN ('Bart', 'Lisa', 'Maggie');

-- For query2
SELECT "id", "first_name", "last_name", "age"
FROM "Person"
WHERE "first_name" NOT IN ('Homer', 'Hugo');

-- For query3
SELECT "id", "first_name", "last_name", "age"
FROM "Person"
WHERE "first_name" IN ('Bart', 'Lisa', 'Maggie');

-- For query4
SELECT "id", "first_name", "last_name", "age"
FROM "Person"
WHERE "first_name" NOT IN ('Homer', 'Hugo');
```

#### Ordering

Specify column ordering in queries:
Expand Down
14 changes: 14 additions & 0 deletions include/sqlgen/col.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ struct Col {
/// Returns the column name.
std::string name() const noexcept { return Name().str(); }

/// Returns an IN condition.
template <class... Ts>
auto in(const Ts&... _ts) const noexcept {
return transpilation::make_condition(transpilation::conditions::in(
transpilation::Col<_name, _alias>{}, _ts...));
}

/// Returns an IS NULL condition.
auto is_null() const noexcept {
return transpilation::make_condition(transpilation::conditions::is_null(
Expand All @@ -64,6 +71,13 @@ struct Col {
transpilation::Col<_name, _alias>{}, _pattern));
}

/// Returns a NOT IN condition.
template <class... Ts>
auto not_in(const Ts&... _ts) const noexcept {
return transpilation::make_condition(transpilation::conditions::not_in(
transpilation::Col<_name, _alias>{}, _ts...));
}

/// Returns a SET clause in an UPDATE statement.
template <class T>
auto set(const T& _to) const noexcept {
Expand Down
16 changes: 13 additions & 3 deletions include/sqlgen/dynamic/Condition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include <rfl.hpp>

#include "../Ref.hpp"

Check warning on line 6 in include/sqlgen/dynamic/Condition.hpp

View workflow job for this annotation

GitHub Actions / (windows-mysql)

relative include path contains '..'

Check warning on line 6 in include/sqlgen/dynamic/Condition.hpp

View workflow job for this annotation

GitHub Actions / (windows-mysql)

relative include path contains '..'

Check warning on line 6 in include/sqlgen/dynamic/Condition.hpp

View workflow job for this annotation

GitHub Actions / (windows-sqlite)

relative include path contains '..'

Check warning on line 6 in include/sqlgen/dynamic/Condition.hpp

View workflow job for this annotation

GitHub Actions / (windows-sqlite)

relative include path contains '..'

Check warning on line 6 in include/sqlgen/dynamic/Condition.hpp

View workflow job for this annotation

GitHub Actions / (windows-postgres)

relative include path contains '..'

Check warning on line 6 in include/sqlgen/dynamic/Condition.hpp

View workflow job for this annotation

GitHub Actions / (windows-postgres)

relative include path contains '..'
#include "Column.hpp"
#include "ColumnOrValue.hpp"
#include "Operation.hpp"
Expand Down Expand Up @@ -31,6 +31,11 @@
Operation op2;
};

struct In {
Operation op;
std::vector<dynamic::Value> patterns;
};

struct IsNotNull {
Operation op;
};
Expand Down Expand Up @@ -68,15 +73,20 @@
dynamic::Value pattern;
};

struct NotIn {
Operation op;
std::vector<dynamic::Value> patterns;
};

struct Or {
Ref<Condition> cond1;
Ref<Condition> cond2;
};

using ReflectionType =
rfl::TaggedUnion<"what", And, Equal, GreaterEqual, GreaterThan, IsNull,
IsNotNull, LesserEqual, LesserThan, Like, Not, NotEqual,
NotLike, Or>;
rfl::TaggedUnion<"what", And, Equal, GreaterEqual, GreaterThan, In,
IsNull, IsNotNull, LesserEqual, LesserThan, Like, Not,
NotEqual, NotIn, NotLike, Or>;

const ReflectionType& reflection() const { return val; }

Expand Down
58 changes: 58 additions & 0 deletions include/sqlgen/transpilation/conditions.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#ifndef SQLGEN_TRANSPILATION_CONDITIONS_HPP_
#define SQLGEN_TRANSPILATION_CONDITIONS_HPP_

#include <rfl.hpp>

#include "string_t.hpp"

namespace sqlgen::transpilation::conditions {

template <class CondType1, class CondType2>
Expand Down Expand Up @@ -53,6 +57,33 @@ auto greater_than(const OpType1& _op1, const OpType2& _op2) {
std::remove_cvref_t<OpType2>>{.op1 = _op1, .op2 = _op2};
}

template <class OpType, class... Ts>
struct In {
using ResultType = bool;

OpType op;
rfl::Tuple<Ts...> patterns;
};

template <class OpType, class... Ts>
auto in(const OpType& _op, const Ts&... _ts) {
return In<OpType, string_t<Ts>...>{
.op = _op, .patterns = rfl::Tuple<string_t<Ts>...>(_ts...)};
}

template <class OpType, class T>
struct InVec {
using ResultType = bool;

OpType op;
std::vector<T> patterns;
};

template <class OpType, class T>
auto in(const OpType& _op, const std::vector<T>& _patterns) {
return InVec<OpType, T>{.op = _op, .patterns = _patterns};
}

template <class OpType>
struct IsNull {
using ResultType = bool;
Expand Down Expand Up @@ -139,6 +170,33 @@ auto not_equal(const OpType1& _op1, const OpType2& _op2) {
.op1 = _op1, .op2 = _op2};
}

template <class OpType, class... Ts>
struct NotIn {
using ResultType = bool;

OpType op;
rfl::Tuple<Ts...> patterns;
};

template <class OpType, class... Ts>
auto not_in(const OpType& _op, const Ts&... _ts) {
return NotIn<OpType, string_t<Ts>...>{
.op = _op, .patterns = rfl::Tuple<string_t<Ts>...>(_ts...)};
}

template <class OpType, class T>
struct NotInVec {
using ResultType = bool;

OpType op;
std::vector<T> patterns;
};

template <class OpType, class T>
auto not_in(const OpType& _op, const std::vector<T>& _patterns) {
return NotInVec<OpType, T>{.op = _op, .patterns = _patterns};
}

template <class OpType>
struct NotLike {
using ResultType = bool;
Expand Down
28 changes: 28 additions & 0 deletions include/sqlgen/transpilation/string_t.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#ifndef SQLGEN_TRANSPILATION_STRINGT_HPP_
#define SQLGEN_TRANSPILATION_STRINGT_HPP_

#include <string>
#include <type_traits>

namespace sqlgen::transpilation {

template <class T>
struct StringType;

template <class T>
struct StringType {
using Type = T;
};

template <class T>
requires std::is_convertible_v<T, std::string>
struct StringType<T> {
using Type = std::string;
};

template <class T>
using string_t = typename StringType<std::remove_cvref_t<T>>::Type;

} // namespace sqlgen::transpilation

#endif
85 changes: 85 additions & 0 deletions include/sqlgen/transpilation/to_condition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@

#include <concepts>
#include <optional>
#include <ranges>
#include <type_traits>
#include <vector>

#include "../Ref.hpp"
#include "../Result.hpp"
#include "../dynamic/Condition.hpp"
#include "../internal/collect/vector.hpp"
#include "Condition.hpp"
#include "all_columns_exist.hpp"
#include "conditions.hpp"
#include "is_timestamp.hpp"
#include "make_field.hpp"
#include "remove_nullable_t.hpp"
#include "to_transpilation_type.hpp"
#include "underlying_t.hpp"

Expand Down Expand Up @@ -141,6 +144,47 @@ struct ToCondition<T, conditions::Like<OpType>> {
}
};

template <class T, class OpType, class... PatternTypes>
struct ToCondition<T, conditions::In<OpType, PatternTypes...>> {
using UnderlyingT = remove_nullable_t<underlying_t<T, OpType>>;

static constexpr bool is_equality_comparable =
(true && ... && std::equality_comparable_with<UnderlyingT, PatternTypes>);

static_assert(is_equality_comparable, "Must be equality comparable.");

dynamic::Condition operator()(const auto& _cond) const {
return dynamic::Condition{
.val = dynamic::Condition::In{.op = make_field<T>(_cond.op).val,
.patterns = rfl::apply(
[](const auto&... _p) {
return std::vector<dynamic::Value>(
{to_value(_p)...});
},
_cond.patterns)}};
}
};

template <class T, class OpType, class PatternType>
struct ToCondition<T, conditions::InVec<OpType, PatternType>> {
using UnderlyingT = remove_nullable_t<underlying_t<T, OpType>>;

static constexpr bool is_equality_comparable =
std::equality_comparable_with<UnderlyingT, PatternType>;

static_assert(is_equality_comparable, "Must be equality comparable.");

dynamic::Condition operator()(const auto& _cond) const {
using namespace std::ranges::views;
return dynamic::Condition{
.val = dynamic::Condition::In{
.op = make_field<T>(_cond.op).val,
.patterns = sqlgen::internal::collect::vector(
_cond.patterns |
transform([](const auto& _v) { return to_value(_v); }))}};
}
};

template <class T, class OpType>
struct ToCondition<T, conditions::IsNotNull<OpType>> {
dynamic::Condition operator()(const auto& _cond) const {
Expand Down Expand Up @@ -198,6 +242,47 @@ struct ToCondition<T, conditions::NotLike<OpType>> {
}
};

template <class T, class OpType, class... PatternTypes>
struct ToCondition<T, conditions::NotIn<OpType, PatternTypes...>> {
using UnderlyingT = remove_nullable_t<underlying_t<T, OpType>>;

static constexpr bool is_equality_comparable =
(true && ... && std::equality_comparable_with<UnderlyingT, PatternTypes>);

static_assert(is_equality_comparable, "Must be equality comparable.");

dynamic::Condition operator()(const auto& _cond) const {
return dynamic::Condition{.val = dynamic::Condition::NotIn{
.op = make_field<T>(_cond.op).val,
.patterns = rfl::apply(
[](const auto&... _p) {
return std::vector<dynamic::Value>(
{to_value(_p)...});
},
_cond.patterns)}};
}
};

template <class T, class OpType, class PatternType>
struct ToCondition<T, conditions::NotInVec<OpType, PatternType>> {
using UnderlyingT = remove_nullable_t<underlying_t<T, OpType>>;

static constexpr bool is_equality_comparable =
std::equality_comparable_with<UnderlyingT, PatternType>;

static_assert(is_equality_comparable, "Must be equality comparable.");

dynamic::Condition operator()(const auto& _cond) const {
using namespace std::ranges::views;
return dynamic::Condition{
.val = dynamic::Condition::NotIn{
.op = make_field<T>(_cond.op).val,
.patterns = sqlgen::internal::collect::vector(
_cond.patterns |
transform([](const auto& _v) { return to_value(_v); }))}};
}
};

template <class T, class CondType1, class CondType2>
struct ToCondition<T, conditions::Or<CondType1, CondType2>> {
dynamic::Condition operator()(const auto& _cond) const {
Expand Down
18 changes: 18 additions & 0 deletions src/sqlgen/mysql/to_sql.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ std::string condition_to_sql(const dynamic::Condition& _cond) noexcept {

template <class ConditionType>
std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
using namespace std::ranges::views;

using C = std::remove_cvref_t<ConditionType>;

std::stringstream stream;
Expand All @@ -229,6 +231,14 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
stream << operation_to_sql(_condition.op1) << " > "
<< operation_to_sql(_condition.op2);

} else if constexpr (std::is_same_v<C, dynamic::Condition::In>) {
stream << operation_to_sql(_condition.op) << " IN ("
<< internal::strings::join(
", ",
internal::collect::vector(_condition.patterns |
transform(column_or_value_to_sql)))
<< ")";

} else if constexpr (std::is_same_v<C, dynamic::Condition::IsNull>) {
stream << operation_to_sql(_condition.op) << " IS NULL";

Expand Down Expand Up @@ -262,6 +272,14 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
stream << "(" << condition_to_sql(*_condition.cond1) << ") OR ("
<< condition_to_sql(*_condition.cond2) << ")";

} else if constexpr (std::is_same_v<C, dynamic::Condition::NotIn>) {
stream << operation_to_sql(_condition.op) << " NOT IN ("
<< internal::strings::join(
", ",
internal::collect::vector(_condition.patterns |
transform(column_or_value_to_sql)))
<< ")";

} else {
static_assert(rfl::always_false_v<C>, "Not all cases were covered.");
}
Expand Down
Loading
Loading