Added .is_null(), .is_not_null(), .like(), .not_like() (#14)

This commit is contained in:
Dr. Patrick Urbanke (劉自成)
2025-05-26 21:45:00 +02:00
committed by GitHub
parent 97cce29ae6
commit 3d8c4ecbf1
14 changed files with 716 additions and 33 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View 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

View 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

View 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

View 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