mirror of
https://github.com/getml/sqlgen.git
synced 2026-01-10 03:20:00 -06:00
Added .is_null(), .is_not_null(), .like(), .not_like() (#14)
This commit is contained in:
committed by
GitHub
parent
97cce29ae6
commit
3d8c4ecbf1
@@ -5,16 +5,19 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab
|
||||
## Core Concepts
|
||||
|
||||
- [Defining Tables](defining_tables.md) - How to define tables using C++ structs
|
||||
- [sqlgen::Result](result.md) - How sqlgen handles errors and results
|
||||
- [sqlgen::PrimaryKey](primary_key.md) - How to define primary keys in sqlgen
|
||||
- [sqlgen::col](col.md) - How to represent columns in queries
|
||||
- [sqlgen::Flatten](flatten.md) - How to "inherit" fields from other structs
|
||||
- [sqlgen::PrimaryKey](primary_key.md) - How to define primary keys in sqlgen
|
||||
- [sqlgen::Result](result.md) - How sqlgen handles errors and results
|
||||
- [sqlgen::to_sql](to_sql.md) - How to transpile C++ operations to dialect-specific SQL
|
||||
|
||||
## Database Operations
|
||||
## Database I/O
|
||||
|
||||
- [sqlgen::read](reading.md) - How to read data from a database
|
||||
- [sqlgen::write](writing.md) - How to write data to a database
|
||||
|
||||
## Database Operations
|
||||
|
||||
- [sqlgen::create_index](create_index.md) - How to create an index on a table
|
||||
- [sqlgen::create_table](create_table.md) - How to create a new table
|
||||
- [sqlgen::delete_from](delete_from.md) - How to delete data from a table
|
||||
@@ -23,18 +26,20 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab
|
||||
- [sqlgen::insert](insert.md) - How to insert data within transactions
|
||||
- [sqlgen::update](update.md) - How to update data in a table
|
||||
|
||||
- [Transactions](transactions.md) - How to use transactions for atomic operations
|
||||
- [Connection Pool](connection_pool.md) - How to manage database connections efficiently
|
||||
|
||||
## Data Types and Validation
|
||||
|
||||
- [sqlgen::Pattern](pattern.md) - How to add regex pattern validation to avoid SQL injection
|
||||
- [sqlgen::Timestamp](timestamp.md) - How timestamps work in sqlgen
|
||||
- [sqlgen::Varchar](varchar.md) - How varchars work in sqlgen
|
||||
- [sqlgen::Pattern](pattern.md) - How to add regex pattern validation to avoid SQL injection
|
||||
|
||||
## Other concepts
|
||||
|
||||
- [Connection Pool](connection_pool.md) - How to manage database connections efficiently
|
||||
- [Transactions](transactions.md) - How to use transactions for atomic operations
|
||||
|
||||
## Supported Databases
|
||||
|
||||
- [PostgreSQL](postgres.md) - How to interact with PostgreSQL and compatible databases (Redshift, Aurora, Greenplum)
|
||||
- [PostgreSQL](postgres.md) - How to interact with PostgreSQL and compatible databases (Redshift, Aurora, Greenplum, CockroachDB, ...)
|
||||
- [SQLite](sqlite.md) - How to interact with SQLite3
|
||||
|
||||
For installation instructions, quick start guide, and usage examples, please refer to the [main README](../README.md).
|
||||
|
||||
195
docs/col.md
Normal file
195
docs/col.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# `sqlgen::col`
|
||||
|
||||
`sqlgen::col` provides a type-safe way to reference and manipulate database columns in SQL queries. It enables building complex SQL conditions, ordering, and updates through a fluent C++ interface.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Definition
|
||||
|
||||
Reference a column using the string literal operator:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
// Using string literal operator
|
||||
const auto age_col = "age"_c;
|
||||
|
||||
// Using col template function
|
||||
const auto name_col = col<"first_name">;
|
||||
```
|
||||
|
||||
### Column Operations
|
||||
|
||||
#### Comparison Operations
|
||||
|
||||
Compare columns with values or other columns:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
// Compare with value
|
||||
const auto query1 = read<std::vector<Person>> | where("age"_c > 18);
|
||||
|
||||
// Compare with another column
|
||||
const auto query2 = read<std::vector<Person>> |
|
||||
where("age"_c > "id"_c);
|
||||
```
|
||||
|
||||
This generates SQL like:
|
||||
|
||||
```sql
|
||||
-- For query1
|
||||
SELECT "id", "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
WHERE "age" > 18;
|
||||
|
||||
-- For query2
|
||||
SELECT "id", "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
WHERE "age" > "id";
|
||||
```
|
||||
|
||||
#### NULL Operations
|
||||
|
||||
Check for NULL or NOT NULL values:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
// Find records where age is NULL
|
||||
const auto query1 = read<std::vector<Person>> |
|
||||
where("age"_c.is_null());
|
||||
|
||||
// Find records where age is NOT NULL
|
||||
const auto query2 = read<std::vector<Person>> |
|
||||
where("age"_c.is_not_null());
|
||||
```
|
||||
|
||||
This generates SQL like:
|
||||
|
||||
```sql
|
||||
-- For query1
|
||||
SELECT "id", "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
WHERE "age" IS NULL;
|
||||
|
||||
-- For query2
|
||||
SELECT "id", "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
WHERE "age" IS NOT NULL;
|
||||
```
|
||||
|
||||
#### Pattern Matching
|
||||
|
||||
Use LIKE and NOT LIKE for pattern matching:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
// Find names starting with 'H'
|
||||
const auto query1 = read<std::vector<Person>> |
|
||||
where("first_name"_c.like("H%"));
|
||||
|
||||
// Find names not starting with 'H'
|
||||
const auto query2 = read<std::vector<Person>> |
|
||||
where("first_name"_c.not_like("H%"));
|
||||
```
|
||||
|
||||
This generates SQL like:
|
||||
|
||||
```sql
|
||||
-- For query1
|
||||
SELECT "id", "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
WHERE "first_name" LIKE 'H%';
|
||||
|
||||
-- For query2
|
||||
SELECT "id", "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
WHERE "first_name" NOT LIKE 'H%';
|
||||
```
|
||||
|
||||
#### Ordering
|
||||
|
||||
Specify column ordering in queries:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
// Order by age ascending
|
||||
const auto query1 = read<std::vector<Person>> |
|
||||
order_by("age"_c);
|
||||
|
||||
// Order by age descending
|
||||
const auto query2 = read<std::vector<Person>> |
|
||||
order_by("age"_c.desc());
|
||||
|
||||
// Multiple columns with mixed ordering
|
||||
const auto query3 = read<std::vector<Person>> |
|
||||
order_by("last_name"_c, "first_name"_c.desc());
|
||||
```
|
||||
|
||||
This generates SQL like:
|
||||
|
||||
```sql
|
||||
-- For query1
|
||||
SELECT "id", "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
ORDER BY "age";
|
||||
|
||||
-- For query2
|
||||
SELECT "id", "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
ORDER BY "age" DESC;
|
||||
|
||||
-- For query3
|
||||
SELECT "id", "first_name", "last_name", "age"
|
||||
FROM "Person"
|
||||
ORDER BY "last_name", "first_name" DESC;
|
||||
```
|
||||
|
||||
#### Updates
|
||||
|
||||
Set column values in UPDATE statements:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
|
||||
// Update a single column
|
||||
const auto query1 = update<Person>("age"_c.set(46));
|
||||
|
||||
// Update multiple columns
|
||||
const auto query2 = update<Person>(
|
||||
"first_name"_c.set("last_name"_c),
|
||||
"age"_c.set(100)
|
||||
) | where("first_name"_c == "Hugo");
|
||||
```
|
||||
|
||||
This generates SQL like:
|
||||
|
||||
```sql
|
||||
-- For query1
|
||||
UPDATE "Person" SET "age" = 46;
|
||||
|
||||
-- For query2
|
||||
UPDATE "Person"
|
||||
SET "first_name" = "last_name", "age" = 100
|
||||
WHERE "first_name" = 'Hugo';
|
||||
```
|
||||
|
||||
## Type Safety
|
||||
|
||||
`sqlgen::col` class provides compile-time type safety:
|
||||
|
||||
- Column names are validated at compile time using string literals
|
||||
- SQL injection is prevented through proper escaping and parameterization
|
||||
|
||||
## Notes
|
||||
|
||||
- The class supports all standard SQL comparison operators: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
||||
- Column names are automatically quoted in generated SQL
|
||||
- The class is designed to be used with the query builder interface
|
||||
- All operations are composable and can be chained together
|
||||
- The class supports both value and column-to-column comparisons
|
||||
- String literals are automatically converted to the appropriate SQL type
|
||||
- The class is thread-safe and has no mutable state
|
||||
@@ -22,13 +22,37 @@ struct Col {
|
||||
/// Returns the column name.
|
||||
std::string name() const noexcept { return Name().str(); }
|
||||
|
||||
/// Defines a SET clause in an UPDATE statement.
|
||||
/// Returns an IS NULL condition.
|
||||
auto is_null() const noexcept {
|
||||
return transpilation::make_condition(
|
||||
transpilation::conditions::is_null(*this));
|
||||
}
|
||||
|
||||
/// Returns a IS NOT NULL condition.
|
||||
auto is_not_null() const noexcept {
|
||||
return transpilation::make_condition(
|
||||
transpilation::conditions::is_not_null(*this));
|
||||
}
|
||||
|
||||
/// Returns a LIKE condition.
|
||||
auto like(const std::string& _pattern) const noexcept {
|
||||
return transpilation::make_condition(
|
||||
transpilation::conditions::like(*this, _pattern));
|
||||
}
|
||||
|
||||
/// Returns a NOT LIKE condition.
|
||||
auto not_like(const std::string& _pattern) const noexcept {
|
||||
return transpilation::make_condition(
|
||||
transpilation::conditions::not_like(*this, _pattern));
|
||||
}
|
||||
|
||||
/// Returns a SET clause in an UPDATE statement.
|
||||
template <class T>
|
||||
auto set(const T& _to) const noexcept {
|
||||
return transpilation::Set<Col<_name>, std::remove_cvref_t<T>>{.to = _to};
|
||||
}
|
||||
|
||||
/// Defines a SET clause in an UPDATE statement.
|
||||
/// Returns a SET clause in an UPDATE statement.
|
||||
auto set(const char* _to) const noexcept {
|
||||
return transpilation::Set<Col<_name>, std::string>{.to = _to};
|
||||
}
|
||||
|
||||
@@ -30,9 +30,12 @@ struct Condition {
|
||||
ColumnOrValue op2;
|
||||
};
|
||||
|
||||
struct NotEqual {
|
||||
Column op1;
|
||||
ColumnOrValue op2;
|
||||
struct IsNotNull {
|
||||
Column op;
|
||||
};
|
||||
|
||||
struct IsNull {
|
||||
Column op;
|
||||
};
|
||||
|
||||
struct LesserEqual {
|
||||
@@ -45,14 +48,30 @@ struct Condition {
|
||||
ColumnOrValue op2;
|
||||
};
|
||||
|
||||
struct Like {
|
||||
Column op;
|
||||
dynamic::Value pattern;
|
||||
};
|
||||
|
||||
struct NotEqual {
|
||||
Column op1;
|
||||
ColumnOrValue op2;
|
||||
};
|
||||
|
||||
struct NotLike {
|
||||
Column op;
|
||||
dynamic::Value pattern;
|
||||
};
|
||||
|
||||
struct Or {
|
||||
Ref<Condition> cond1;
|
||||
Ref<Condition> cond2;
|
||||
};
|
||||
|
||||
using ReflectionType =
|
||||
rfl::TaggedUnion<"what", And, Equal, GreaterEqual, GreaterThan, NotEqual,
|
||||
LesserEqual, LesserThan, Or>;
|
||||
rfl::TaggedUnion<"what", And, Equal, GreaterEqual, GreaterThan, IsNull,
|
||||
IsNotNull, LesserEqual, LesserThan, Like, NotEqual,
|
||||
NotLike, Or>;
|
||||
|
||||
const ReflectionType& reflection() const { return val; }
|
||||
|
||||
|
||||
@@ -45,16 +45,24 @@ auto greater_than(const OpType1& _op1, const OpType2& _op2) {
|
||||
std::remove_cvref_t<OpType2>>{.op1 = _op1, .op2 = _op2};
|
||||
}
|
||||
|
||||
template <class OpType1, class OpType2>
|
||||
struct NotEqual {
|
||||
OpType1 op1;
|
||||
OpType2 op2;
|
||||
template <class OpType>
|
||||
struct IsNull {
|
||||
OpType op;
|
||||
};
|
||||
|
||||
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>>{
|
||||
.op1 = _op1, .op2 = _op2};
|
||||
template <class OpType>
|
||||
auto is_null(const OpType& _op) {
|
||||
return IsNull<std::remove_cvref_t<OpType>>{.op = _op};
|
||||
}
|
||||
|
||||
template <class OpType>
|
||||
struct IsNotNull {
|
||||
OpType op;
|
||||
};
|
||||
|
||||
template <class OpType>
|
||||
auto is_not_null(const OpType& _op) {
|
||||
return IsNotNull<std::remove_cvref_t<OpType>>{.op = _op};
|
||||
}
|
||||
|
||||
template <class OpType1, class OpType2>
|
||||
@@ -81,6 +89,40 @@ auto lesser_than(const OpType1& _op1, const OpType2& _op2) {
|
||||
.op1 = _op1, .op2 = _op2};
|
||||
}
|
||||
|
||||
template <class OpType>
|
||||
struct Like {
|
||||
OpType op;
|
||||
std::string pattern;
|
||||
};
|
||||
|
||||
template <class OpType>
|
||||
auto like(const OpType& _op, const std::string& _pattern) {
|
||||
return Like<std::remove_cvref_t<OpType>>{.op = _op, .pattern = _pattern};
|
||||
}
|
||||
|
||||
template <class OpType1, class OpType2>
|
||||
struct NotEqual {
|
||||
OpType1 op1;
|
||||
OpType2 op2;
|
||||
};
|
||||
|
||||
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>>{
|
||||
.op1 = _op1, .op2 = _op2};
|
||||
}
|
||||
|
||||
template <class OpType>
|
||||
struct NotLike {
|
||||
OpType op;
|
||||
std::string pattern;
|
||||
};
|
||||
|
||||
template <class OpType>
|
||||
auto not_like(const OpType& _op, const std::string& _pattern) {
|
||||
return NotLike<std::remove_cvref_t<OpType>>{.op = _op, .pattern = _pattern};
|
||||
}
|
||||
|
||||
template <class CondType1, class CondType2>
|
||||
struct Or {
|
||||
CondType1 cond1;
|
||||
|
||||
@@ -168,6 +168,37 @@ struct ToCondition<T, conditions::LesserThan<Col<_name>, Value<V>>> {
|
||||
}
|
||||
};
|
||||
|
||||
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.");
|
||||
|
||||
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)}};
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
return dynamic::Condition{.val = dynamic::Condition::IsNotNull{
|
||||
.op = dynamic::Column{.name = _name.str()}}};
|
||||
}
|
||||
};
|
||||
|
||||
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, rfl::internal::StringLiteral _name1,
|
||||
rfl::internal::StringLiteral _name2>
|
||||
struct ToCondition<T, conditions::NotEqual<Col<_name1>, Col<_name2>>> {
|
||||
@@ -194,6 +225,17 @@ struct ToCondition<T, conditions::NotEqual<Col<_name>, Value<V>>> {
|
||||
}
|
||||
};
|
||||
|
||||
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.");
|
||||
|
||||
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)}};
|
||||
}
|
||||
};
|
||||
|
||||
template <class T, class CondType1, class CondType2>
|
||||
struct ToCondition<T, conditions::Or<CondType1, CondType2>> {
|
||||
dynamic::Condition operator()(const auto& _cond) const {
|
||||
|
||||
@@ -30,6 +30,8 @@ std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept;
|
||||
|
||||
std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept;
|
||||
|
||||
std::string escape_single_quote(const std::string& _str) noexcept;
|
||||
|
||||
std::vector<std::string> get_primary_keys(
|
||||
const dynamic::CreateTable& _stmt) noexcept;
|
||||
|
||||
@@ -63,7 +65,7 @@ std::string column_or_value_to_sql(
|
||||
const auto handle_value = [](const auto& _v) -> std::string {
|
||||
using Type = std::remove_cvref_t<decltype(_v)>;
|
||||
if constexpr (std::is_same_v<Type, dynamic::String>) {
|
||||
return "'" + _v.val + "'";
|
||||
return "'" + escape_single_quote(_v.val) + "'";
|
||||
} else {
|
||||
return std::to_string(_v.val);
|
||||
}
|
||||
@@ -105,9 +107,11 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
|
||||
stream << column_or_value_to_sql(_condition.op1) << " > "
|
||||
<< column_or_value_to_sql(_condition.op2);
|
||||
|
||||
} 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);
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::IsNull>) {
|
||||
stream << column_or_value_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";
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::LesserEqual>) {
|
||||
stream << column_or_value_to_sql(_condition.op1)
|
||||
@@ -117,12 +121,24 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
|
||||
stream << column_or_value_to_sql(_condition.op1) << " < "
|
||||
<< column_or_value_to_sql(_condition.op2);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::Like>) {
|
||||
stream << column_or_value_to_sql(_condition.op) << " LIKE "
|
||||
<< column_or_value_to_sql(_condition.pattern);
|
||||
|
||||
} 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);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::NotLike>) {
|
||||
stream << column_or_value_to_sql(_condition.op) << " NOT LIKE "
|
||||
<< column_or_value_to_sql(_condition.pattern);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::Or>) {
|
||||
stream << "(" << condition_to_sql(*_condition.cond1) << ") OR ("
|
||||
<< condition_to_sql(*_condition.cond2) << ")";
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<C>, "Not all cases where covered.");
|
||||
static_assert(rfl::always_false_v<C>, "Not all cases were covered.");
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
@@ -246,6 +262,10 @@ std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept {
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string escape_single_quote(const std::string& _str) noexcept {
|
||||
return internal::strings::replace_all(_str, "'", "''");
|
||||
}
|
||||
|
||||
std::vector<std::string> get_primary_keys(
|
||||
const dynamic::CreateTable& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
@@ -26,6 +26,8 @@ std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept;
|
||||
|
||||
std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept;
|
||||
|
||||
std::string escape_single_quote(const std::string& _str) noexcept;
|
||||
|
||||
template <class InsertOrWrite>
|
||||
std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept;
|
||||
|
||||
@@ -44,7 +46,7 @@ std::string column_or_value_to_sql(
|
||||
const auto handle_value = [](const auto& _v) -> std::string {
|
||||
using Type = std::remove_cvref_t<decltype(_v)>;
|
||||
if constexpr (std::is_same_v<Type, dynamic::String>) {
|
||||
return "'" + _v.val + "'";
|
||||
return "'" + escape_single_quote(_v.val) + "'";
|
||||
} else {
|
||||
return std::to_string(_v.val);
|
||||
}
|
||||
@@ -92,9 +94,11 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
|
||||
stream << column_or_value_to_sql(_condition.op1) << " > "
|
||||
<< column_or_value_to_sql(_condition.op2);
|
||||
|
||||
} 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);
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::IsNull>) {
|
||||
stream << column_or_value_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";
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::LesserEqual>) {
|
||||
stream << column_or_value_to_sql(_condition.op1)
|
||||
@@ -104,12 +108,24 @@ std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
|
||||
stream << column_or_value_to_sql(_condition.op1) << " < "
|
||||
<< column_or_value_to_sql(_condition.op2);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::Like>) {
|
||||
stream << column_or_value_to_sql(_condition.op) << " LIKE "
|
||||
<< column_or_value_to_sql(_condition.pattern);
|
||||
|
||||
} 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);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::NotLike>) {
|
||||
stream << column_or_value_to_sql(_condition.op) << " NOT LIKE "
|
||||
<< column_or_value_to_sql(_condition.pattern);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::Or>) {
|
||||
stream << "(" << condition_to_sql(*_condition.cond1) << ") OR ("
|
||||
<< condition_to_sql(*_condition.cond2) << ")";
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<C>, "Not all cases where covered.");
|
||||
static_assert(rfl::always_false_v<C>, "Not all cases were covered.");
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
@@ -220,6 +236,10 @@ std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept {
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string escape_single_quote(const std::string& _str) noexcept {
|
||||
return internal::strings::replace_all(_str, "'", "''");
|
||||
}
|
||||
|
||||
template <class InsertOrWrite>
|
||||
std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
62
tests/postgres/test_is_null.cpp
Normal file
62
tests/postgres/test_is_null.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#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_is_null {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::optional<int> age;
|
||||
};
|
||||
|
||||
TEST(postgres, test_is_null) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{.id = 0, .first_name = "Homer", .last_name = "Simpson"},
|
||||
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"}});
|
||||
|
||||
const auto credentials = sqlgen::postgres::Credentials{.user = "postgres",
|
||||
.password = "password",
|
||||
.host = "localhost",
|
||||
.dbname = "postgres"};
|
||||
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto conn = postgres::connect(credentials);
|
||||
|
||||
const auto people2 =
|
||||
conn.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c.is_null()) | order_by("first_name"_c.desc()))
|
||||
.value();
|
||||
|
||||
const auto people3 =
|
||||
conn.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c.is_not_null()) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const std::string expected1 =
|
||||
R"([{"id":4,"first_name":"Hugo","last_name":"Simpson"},{"id":0,"first_name":"Homer","last_name":"Simpson"}])";
|
||||
|
||||
const std::string expected2 =
|
||||
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), expected1);
|
||||
EXPECT_EQ(rfl::json::write(people3), expected2);
|
||||
}
|
||||
|
||||
} // namespace test_is_null
|
||||
|
||||
#endif
|
||||
32
tests/postgres/test_is_null_dry.cpp
Normal file
32
tests/postgres/test_is_null_dry.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/postgres.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_is_null_dry {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::optional<int> age;
|
||||
};
|
||||
|
||||
TEST(postgres, test_is_null_dry) {
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto sql = postgres::to_sql(sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c.is_null()) |
|
||||
order_by("first_name"_c.desc()));
|
||||
|
||||
const std::string expected =
|
||||
R"(SELECT "id", "first_name", "last_name", "age" FROM "Person" WHERE "age" IS NULL ORDER BY "first_name" DESC;)";
|
||||
|
||||
EXPECT_EQ(sql, expected);
|
||||
}
|
||||
|
||||
} // namespace test_is_null_dry
|
||||
|
||||
72
tests/postgres/test_like.cpp
Normal file
72
tests/postgres/test_like.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#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_like {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(postgres, test_like) {
|
||||
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 = postgres::connect(credentials);
|
||||
|
||||
const auto people2 =
|
||||
conn.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.like("H%")) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const auto people3 =
|
||||
conn.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.not_like("H%")) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const auto people4 =
|
||||
conn.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.like("O'Reilly")) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const std::string expected1 =
|
||||
R"([{"id":4,"first_name":"Hugo","last_name":"Simpson","age":10},{"id":0,"first_name":"Homer","last_name":"Simpson","age":45}])";
|
||||
|
||||
const std::string expected2 =
|
||||
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}])";
|
||||
|
||||
const std::string expected3 = R"([])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected1);
|
||||
EXPECT_EQ(rfl::json::write(people3), expected2);
|
||||
EXPECT_EQ(rfl::json::write(people4), expected3);
|
||||
}
|
||||
|
||||
} // namespace test_like
|
||||
|
||||
#endif
|
||||
32
tests/postgres/test_like_dry.cpp
Normal file
32
tests/postgres/test_like_dry.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/postgres.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_like_dry {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::optional<int> age;
|
||||
};
|
||||
|
||||
TEST(postgres, test_like_dry) {
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto sql =
|
||||
postgres::to_sql(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.like("H%")) | order_by("age"_c));
|
||||
|
||||
const std::string expected =
|
||||
R"(SELECT "id", "first_name", "last_name", "age" FROM "Person" WHERE "first_name" LIKE 'H%' ORDER BY "age";)";
|
||||
|
||||
EXPECT_EQ(sql, expected);
|
||||
}
|
||||
|
||||
} // namespace test_like_dry
|
||||
|
||||
54
tests/sqlite/test_is_null.cpp
Normal file
54
tests/sqlite/test_is_null.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/sqlite.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_is_null {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::optional<int> age;
|
||||
};
|
||||
|
||||
TEST(sqlite, test_is_null) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{.id = 0, .first_name = "Homer", .last_name = "Simpson"},
|
||||
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"}});
|
||||
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto conn = sqlite::connect();
|
||||
|
||||
const auto people2 =
|
||||
conn.and_then(write(std::ref(people1)))
|
||||
.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c.is_null()) | order_by("first_name"_c.desc()))
|
||||
.value();
|
||||
|
||||
const auto people3 =
|
||||
conn.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c.is_not_null()) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const std::string expected1 =
|
||||
R"([{"id":4,"first_name":"Hugo","last_name":"Simpson"},{"id":0,"first_name":"Homer","last_name":"Simpson"}])";
|
||||
|
||||
const std::string expected2 =
|
||||
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), expected1);
|
||||
EXPECT_EQ(rfl::json::write(people3), expected2);
|
||||
}
|
||||
|
||||
} // namespace test_is_null
|
||||
|
||||
64
tests/sqlite/test_like.cpp
Normal file
64
tests/sqlite/test_like.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/sqlite.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_like {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(sqlite, test_like) {
|
||||
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}});
|
||||
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto conn = sqlite::connect();
|
||||
|
||||
const auto people2 =
|
||||
conn.and_then(write(std::ref(people1)))
|
||||
.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.like("H%")) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const auto people3 =
|
||||
conn.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.not_like("H%")) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const auto people4 =
|
||||
conn.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.like("O'Reilly")) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const std::string expected1 =
|
||||
R"([{"id":4,"first_name":"Hugo","last_name":"Simpson","age":10},{"id":0,"first_name":"Homer","last_name":"Simpson","age":45}])";
|
||||
|
||||
const std::string expected2 =
|
||||
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}])";
|
||||
|
||||
const std::string expected3 = R"([])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected1);
|
||||
EXPECT_EQ(rfl::json::write(people3), expected2);
|
||||
EXPECT_EQ(rfl::json::write(people4), expected3);
|
||||
}
|
||||
|
||||
} // namespace test_like
|
||||
|
||||
Reference in New Issue
Block a user