Added support for DuckDB (#90)

This commit is contained in:
Dr. Patrick Urbanke (劉自成)
2025-11-20 21:20:12 +01:00
committed by GitHub
parent e07be25a5d
commit 8568a83362
115 changed files with 6609 additions and 12 deletions

View File

@@ -14,7 +14,6 @@ jobs:
- db: postgres
- db: sqlite
- db: mysql
- db: headers
name: "(windows-${{ matrix.db }})"
concurrency:
group: ci-${{ github.ref }}-windows-${{ matrix.db }}
@@ -34,11 +33,6 @@ jobs:
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- uses: ilammy/msvc-dev-cmd@v1
- uses: lukka/run-vcpkg@v11
- name: Compile
if: matrix.db == 'headers'
run: |
cmake -S . -B build -DCMAKE_CXX_STANDARD=20 -DSQLGEN_CHECK_HEADERS=ON
cmake --build build --config Release -j4
- name: Compile
if: matrix.db == 'postgres'
run: |
@@ -55,6 +49,5 @@ jobs:
cmake -S . -B build -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=x64-windows-release
cmake --build build --config Release -j4
- name: Run tests
if: matrix.db != 'headers'
run: |
ctest --test-dir build --output-on-failure

View File

@@ -4,6 +4,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(SQLGEN_BUILD_SHARED "Build shared library" ${BUILD_SHARED_LIBS})
option(SQLGEN_DUCKDB "Enable DuckDB support" OFF)
option(SQLGEN_MYSQL "Enable MySQL support" OFF)
option(SQLGEN_POSTGRES "Enable PostgreSQL support" ON) # enabled by default
@@ -30,6 +32,10 @@ if (SQLGEN_USE_VCPKG)
list(APPEND VCPKG_MANIFEST_FEATURES "tests")
endif()
if (SQLGEN_DUCKDB OR SQLGEN_CHECK_HEADERS)
list(APPEND VCPKG_MANIFEST_FEATURES "duckdb")
endif()
if (SQLGEN_MYSQL OR SQLGEN_CHECK_HEADERS)
list(APPEND VCPKG_MANIFEST_FEATURES "mysql")
endif()
@@ -89,6 +95,16 @@ target_include_directories(
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
if (SQLGEN_DUCKDB OR SQLGEN_CHECK_HEADERS)
list(APPEND SQLGEN_SOURCES src/sqlgen_duckdb.cpp)
if (NOT TARGET DuckDB)
find_package(DuckDB REQUIRED)
endif()
target_link_libraries(sqlgen PUBLIC $<IF:$<TARGET_EXISTS:duckdb>,duckdb,duckdb_static>)
endif()
if(SQLGEN_MYSQL OR SQLGEN_CHECK_HEADERS)
list(APPEND SQLGEN_SOURCES src/sqlgen_mysql.cpp)
if (SQLGEN_USE_VCPKG)

View File

@@ -31,6 +31,7 @@ The following table lists the databases currently supported by sqlgen and the un
| Database | Library | Version | License | Remarks |
|---------------|--------------------------------------------------------------------------|--------------|---------------| -----------------------------------------------------|
| DuckDB | [duckdb](https://github.com/duckdb/duckdb) | >= 1.4.1 | MIT | |
| MySQL/MariaDB | [libmariadb](https://github.com/mariadb-corporation/mariadb-connector-c) | >= 3.4.5 | LGPL | |
| PostgreSQL | [libpq](https://github.com/postgres/postgres) | >= 16.4 | PostgreSQL | Will work for all libpq-compatible databases |
| sqlite | [sqlite](https://sqlite.org/index.html) | >= 3.49.1 | Public Domain | |

View File

@@ -59,6 +59,7 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab
## Supported Databases
- [DuckDB](duckdb.md) - How to interact with DuckDB
- [MySQL](mysql.md) - How to interact with MariaDB and MySQL
- [PostgreSQL](postgres.md) - How to interact with PostgreSQL and compatible databases (Redshift, Aurora, Greenplum, CockroachDB, ...)
- [SQLite](sqlite.md) - How to interact with SQLite3

142
docs/duckdb.md Normal file
View File

@@ -0,0 +1,142 @@
∂# `sqlgen::duckdb`
The `sqlgen::duckdb` module provides a type-safe and efficient interface for interacting with DuckDB databases. It implements the core database operations through a connection-based API with support for prepared statements, transactions, and efficient data iteration.
## Usage
### Basic Connection
Create a connection to a DuckDB database:
```cpp
// Connect to an in-memory database
const auto conn = sqlgen::duckdb::connect();
// Connect to a file-based database
const auto conn = sqlgen::duckdb::connect("database.db");
```
The type of `conn` is `sqlgen::Result<sqlgen::Ref<sqlgen::duckdb::Connection>>`, which is useful for error handling:
```cpp
// Handle connection errors
const auto conn = sqlgen::duckdb::connect("database.db");
if (!conn) {
// Handle error...
return;
}
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>> |
where("age"_c < 18 and "first_name"_c != "Hugo");
// Use the connection
const auto minors = query(conn);
```
### Basic Operations
Write data to the database:
```cpp
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
const auto people = 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}
};
// Write data to database
const auto result = sqlgen::write(conn, people);
```
Read data with filtering and ordering:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Read all people ordered by age
const auto all_people = sqlgen::read<std::vector<Person>> |
order_by("age"_c);
// Read minors only
const auto minors = sqlgen::read<std::vector<Person>> |
where("age"_c < 18) |
order_by("age"_c);
// Use the queries
const auto result1 = all_people(conn);
const auto result2 = minors(conn);
```
### Transactions
Perform operations within transactions:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Delete a person and update another in a transaction
const auto delete_hugo = delete_from<Person> |
where("first_name"_c == "Hugo");
const auto update_homer = update<Person>("age"_c.set(46)) |
where("first_name"_c == "Homer");
const auto result = begin_transaction(conn)
.and_then(delete_hugo)
.and_then(update_homer)
.and_then(commit)
.value();
```
### Update Operations
Update data in a table:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Update multiple columns
const auto query = update<Person>("first_name"_c.set("last_name"_c), "age"_c.set(100)) |
where("first_name"_c == "Hugo");
query(conn).value();
```
## Notes
- The module provides a type-safe interface for DuckDB operations
- All operations return `sqlgen::Result<T>` for error handling
- Prepared statements are used for efficient query execution
- The iterator interface supports batch processing of results
- SQL generation adapts to DuckDB's dialect
- The module supports:
- In-memory and file-based databases
- Transactions (begin, commit, rollback)
- Efficient batch operations
- Type-safe SQL generation
- Error handling through `Result<T>`
- Resource management through `Ref<T>`
- Auto-incrementing primary keys
- Various data types including VARCHAR, TIMESTAMP, DATE
- Complex queries with WHERE clauses, ORDER BY, LIMIT, JOINs
- LIKE and pattern matching operations
- Mathematical operations and string functions
- JSON data types
- Foreign keys and referential integrity
- Unique constraints
- Views and materialized views
- Indexes
```

View File

@@ -45,6 +45,50 @@ struct Parser<boost::uuids::uuid> {
} // namespace sqlgen::parsing
```
### DuckDB parser specialization
**Important:** If you're using DuckDB, you must also implement a separate parser specialization in the `sqlgen::duckdb::parsing` namespace. This is required for performance reasons, as DuckDB uses its own native types and appender interface.
The DuckDB parser has a different interface than the generic parser:
```cpp
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <sqlgen/duckdb.hpp>
#include <exception>
namespace sqlgen::duckdb::parsing {
template <>
struct Parser<boost::uuids::uuid> {
using ResultingType = duckdb_string_t;
static Result<boost::uuids::uuid> read(const ResultingType* _r) noexcept {
return Parser<std::string>::read(_r).and_then(
[&](const std::string& _str) -> Result<boost::uuids::uuid> {
try {
return boost::lexical_cast<boost::uuids::uuid>(_str);
} catch (const std::exception& e) {
return error(e.what());
}
});
}
static Result<Nothing> write(const boost::uuids::uuid& _u,
duckdb_appender _appender) noexcept {
return Parser<std::string>::write(boost::uuids::to_string(_u), _appender);
}
};
} // namespace sqlgen::duckdb::parsing
```
Key differences from the generic parser:
- `read` takes `const ResultingType*` (where `ResultingType = duckdb_string_t`) instead of `const std::optional<std::string>&`
- `write` takes a `duckdb_appender` parameter and returns `Result<Nothing>` instead of `std::optional<std::string>`
- No `to_type()` method is required (the generic parser's `to_type()` is used for schema generation)
The second step is to specialize `sqlgen::transpilation::ToValue` for `boost::uuids::uuid` and implement `operator()`:
```cpp
@@ -157,6 +201,15 @@ static dynamic::Type to_type() noexcept {
}
```
- DuckDB:
```cpp
static dynamic::Type to_type() noexcept {
return sqlgen::dynamic::types::Dynamic{"TEXT"};
}
```
Note: For DuckDB, you must also implement the `sqlgen::duckdb::parsing::Parser` specialization as shown in the DuckDB parser specialization section above.
## Parser specialization requirements
Specializing `sqlgen::parsing::Parser<T>` requires three methods. These guidelines help ensure correctness and portability:
@@ -200,4 +253,5 @@ Additional best practices:
- Works with all operations: `create_table`, `insert`, `select`, `update`, `delete`
- The type name is passed directly to the database; ensure it is valid for the target dialect
- Keep specializations in the `sqlgen::parsing` namespace
- **DuckDB users:** You must implement both `sqlgen::parsing::Parser` and `sqlgen::duckdb::parsing::Parser` specializations for your custom type

View File

@@ -23,7 +23,7 @@ const auto creds = sqlgen::mysql::Credentials{
const auto conn = sqlgen::mysql::connect(creds);
```
The connection is wrapped in a `sqlgen::Result<Ref<Connection>>` for error handling:
The type of `conn` is `sqlgen::Result<sqlgen::Ref<sqlgen::mysql::Connection>>`, which is useful for error handling:
```cpp
// Handle connection errors

View File

@@ -22,7 +22,7 @@ const auto creds = sqlgen::postgres::Credentials{
const auto conn = sqlgen::postgres::connect(creds);
```
The connection is wrapped in a `sqlgen::Result<Ref<Connection>>` for error handling:
The type of `conn` is `sqlgen::Result<sqlgen::Ref<sqlgen::postgres::Connection>>`, which is useful for error handling:
```cpp
// Handle connection errors

View File

@@ -16,7 +16,7 @@ const auto conn = sqlgen::sqlite::connect();
const auto conn = sqlgen::sqlite::connect("database.db");
```
The connection is wrapped in a `sqlgen::Result<Ref<Connection>>` for error handling:
The type of `conn` is `sqlgen::Result<sqlgen::Ref<sqlgen::sqlite::Connection>>`, which is useful for error handling:
```cpp
// Handle connection errors

View File

@@ -0,0 +1,8 @@
#ifndef SQLGEN_DUCKDB_HPP_
#define SQLGEN_DUCKDB_HPP_
#include "../sqlgen.hpp"
#include "duckdb/connect.hpp"
#include "duckdb/to_sql.hpp"
#endif

View File

@@ -0,0 +1,33 @@
#ifndef SQLGEN_DUCKDB_COLUMNDATA_HPP_
#define SQLGEN_DUCKDB_COLUMNDATA_HPP_
#include <duckdb.h>
#include <memory>
#include <rfl.hpp>
#include <rfl/internal/StringLiteral.hpp>
#include <vector>
namespace sqlgen::duckdb {
template <class T, class _ColName>
struct ColumnData {
using ColName = _ColName;
duckdb_vector vec;
T *data;
uint64_t *validity;
// This is only needed if the data returned by DuckDB is not of the
// same type as T, but can be converted to T. In this case,
// data actually points to ptr->data(). Otherwise, ptr is a nullptr.
std::shared_ptr<std::vector<T>> ptr;
bool is_not_null(idx_t _i) const {
return (validity == nullptr) || duckdb_validity_row_is_valid(validity, _i);
}
};
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,209 @@
#ifndef SQLGEN_DUCKDB_CONNECTION_HPP_
#define SQLGEN_DUCKDB_CONNECTION_HPP_
#include <duckdb.h>
#include <iterator>
#include <memory>
#include <optional>
#include <rfl.hpp>
#include <sstream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include "../Range.hpp"
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../Transaction.hpp"
#include "../dynamic/Operation.hpp"
#include "../dynamic/SelectFrom.hpp"
#include "../dynamic/Write.hpp"
#include "../internal/iterator_t.hpp"
#include "../internal/remove_auto_incr_primary_t.hpp"
#include "../internal/to_container.hpp"
#include "../is_connection.hpp"
#include "../sqlgen_api.hpp"
#include "./parsing/Parser_default.hpp"
#include "DuckDBAppender.hpp"
#include "DuckDBConnection.hpp"
#include "DuckDBResult.hpp"
#include "Iterator.hpp"
#include "to_sql.hpp"
namespace sqlgen::duckdb {
class SQLGEN_API Connection {
using ConnPtr = Ref<DuckDBConnection>;
public:
Connection(const ConnPtr &_conn) : appender_(nullptr), conn_(_conn) {}
static rfl::Result<Ref<Connection>> make(
const std::optional<std::string> &_fname) noexcept;
~Connection() = default;
Result<Nothing> begin_transaction() noexcept;
Result<Nothing> commit() noexcept;
Result<Nothing> execute(const std::string &_sql) noexcept;
template <class ItBegin, class ItEnd>
Result<Nothing> insert(const dynamic::Insert &_insert_stmt, ItBegin _begin,
ItEnd _end) noexcept {
using namespace std::ranges::views;
const auto sql = to_sql(_insert_stmt);
auto columns = internal::collect::vector(
_insert_stmt.columns |
transform([](const auto &_str) { return _str.c_str(); }));
return get_duckdb_logical_types(_insert_stmt.table, _insert_stmt.columns)
.and_then([&](const auto &_types) {
return DuckDBAppender::make(sql, conn_, columns, _types);
})
.and_then([&](auto _appender) {
return write_to_appender(_begin, _end, _appender->appender())
.and_then([&](const auto &) { return _appender->close(); });
});
}
template <class ContainerType>
auto read(const dynamic::SelectFrom &_query) {
using ValueType = transpilation::value_t<ContainerType>;
return internal::to_container<ContainerType, Iterator<ValueType>>(
Iterator<ValueType>(to_sql(_query), conn_));
}
Result<Nothing> rollback() noexcept;
std::string to_sql(const dynamic::Statement &_stmt) noexcept {
return duckdb::to_sql_impl(_stmt);
}
Result<Nothing> start_write(const dynamic::Write &_write_stmt) {
if (appender_) {
return error(
"Write operation already in progress - you cannot start another.");
}
using namespace std::ranges::views;
auto columns = internal::collect::vector(
_write_stmt.columns |
transform([](const auto &_str) { return _str.c_str(); }));
const auto sql = to_sql(_write_stmt);
return get_duckdb_logical_types(_write_stmt.table, _write_stmt.columns)
.and_then([&](auto _types) {
return DuckDBAppender::make(sql, conn_, columns, _types);
})
.transform([&](auto &&_appender) {
appender_ = _appender.ptr();
return Nothing{};
});
}
Result<Nothing> end_write() {
if (!appender_) {
return error("No write operation in progress - nothing to end.");
}
appender_ = nullptr;
return Nothing{};
}
template <class ItBegin, class ItEnd>
Result<Nothing> write(ItBegin _begin, ItEnd _end) {
if (!appender_) {
return error("No write operation in progress - nothing to write.");
}
return write_to_appender(_begin, _end, appender_->appender());
}
private:
Result<std::vector<duckdb_logical_type>> get_duckdb_logical_types(
const dynamic::Table &_table, const std::vector<std::string> &_columns) {
using namespace std::ranges::views;
const auto fields = internal::collect::vector(
_columns | transform([](const auto &_name) {
return dynamic::SelectFrom::Field{
.val = dynamic::Operation{dynamic::Column{.alias = std::nullopt,
.name = _name}},
.as = std::nullopt};
}));
const auto select_from = dynamic::SelectFrom{
.table_or_query = _table, .fields = fields, .limit = dynamic::Limit{0}};
return DuckDBResult::make(to_sql(select_from), conn_)
.transform([&](const auto &_res) {
return internal::collect::vector(
iota(static_cast<idx_t>(0), static_cast<idx_t>(fields.size())) |
transform(
std::bind_front(duckdb_column_logical_type, &_res->res())));
});
}
template <class ItBegin, class ItEnd>
Result<Nothing> write_to_appender(ItBegin _begin, ItEnd _end,
duckdb_appender _appender) {
for (auto it = _begin; it < _end; ++it) {
const auto res = write_row(*it, _appender);
if (!res) {
return res;
}
const auto state = duckdb_appender_end_row(_appender);
if (state == DuckDBError) {
return error(duckdb_appender_error(_appender));
}
}
return Nothing{};
}
template <class StructT>
Result<Nothing> write_row(const StructT &_struct,
duckdb_appender _appender) noexcept {
using ViewType =
internal::remove_auto_incr_primary_t<rfl::view_t<const StructT>>;
try {
ViewType(rfl::to_view(_struct)).apply([&](const auto &_field) {
using ValueType = std::remove_cvref_t<std::remove_pointer_t<
typename std::remove_cvref_t<decltype(_field)>::Type>>;
duckdb::parsing::Parser<ValueType>::write(*_field.value(), _appender)
.value();
});
} catch (const std::exception &e) {
return error(e.what());
}
return Nothing{};
}
private:
/// The appender to be used for the write statements
std::shared_ptr<DuckDBAppender> appender_;
/// The underlying duckdb3 connection.
ConnPtr conn_;
};
static_assert(is_connection<Connection>,
"Must fulfill the is_connection concept.");
static_assert(is_connection<Transaction<Connection>>,
"Must fulfill the is_connection concept.");
} // namespace sqlgen::duckdb
namespace sqlgen::internal {
template <class ValueType>
struct IteratorType<ValueType, duckdb::Connection> {
using Type = duckdb::Iterator<ValueType>;
};
} // namespace sqlgen::internal
#endif

View File

@@ -0,0 +1,84 @@
#ifndef SQLGEN_DUCKDB_DUCKDBAPPENDER_HPP_
#define SQLGEN_DUCKDB_DUCKDBAPPENDER_HPP_
#include <duckdb.h>
#include <string>
#include "../sqlgen_api.hpp"
#include "DuckDBConnection.hpp"
namespace sqlgen::duckdb {
class SQLGEN_API DuckDBAppender {
using ConnPtr = Ref<DuckDBConnection>;
public:
static Result<Ref<DuckDBAppender>> make(
const std::string& _sql, const ConnPtr& _conn,
const std::vector<const char*>& _columns,
const std::vector<duckdb_logical_type>& _types) {
try {
return Ref<DuckDBAppender>::make(_sql, _conn, _columns, _types);
} catch (const std::exception& e) {
return error(e.what());
}
}
DuckDBAppender(const std::string& _sql, const ConnPtr& _conn,
std::vector<const char*> _columns,
std::vector<duckdb_logical_type> _types)
: destroy_(false) {
if (duckdb_appender_create_query(
_conn->conn(), _sql.c_str(), static_cast<idx_t>(_columns.size()),
_types.data(), "sqlgen_appended_data", _columns.data(),
&appender_) == DuckDBError) {
throw std::runtime_error("Could not create appender.");
}
destroy_ = true;
}
~DuckDBAppender() {
if (destroy_) {
duckdb_appender_destroy(&appender_);
}
}
DuckDBAppender(const DuckDBAppender& _other) = delete;
DuckDBAppender(DuckDBAppender&& _other)
: destroy_(_other.destroy_), appender_(_other.appender_) {
_other.destroy_ = false;
}
DuckDBAppender& operator=(const DuckDBAppender& _other) = delete;
DuckDBAppender& operator=(DuckDBAppender&& _other) {
if (this == &_other) {
return *this;
}
destroy_ = _other.destroy_;
appender_ = _other.appender_;
_other.destroy_ = false;
return *this;
}
duckdb_appender& appender() { return appender_; }
Result<Nothing> close() {
const auto state = duckdb_appender_close(appender_);
if (state == DuckDBError) {
return error(duckdb_appender_error(appender_));
}
return Nothing{};
}
private:
bool destroy_;
duckdb_appender appender_;
};
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,52 @@
#ifndef SQLGEN_DUCKDB_DUCKDBCONNECTION_HPP_
#define SQLGEN_DUCKDB_DUCKDBCONNECTION_HPP_
#include <duckdb.h>
#include <optional>
#include <string>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "../sqlgen_api.hpp"
namespace sqlgen::duckdb {
class SQLGEN_API DuckDBConnection {
public:
static Result<Ref<DuckDBConnection>> make(
const std::optional<std::string>& _fname);
DuckDBConnection(duckdb_connection _conn, duckdb_database _db)
: conn_(_conn), db_(_db) {}
~DuckDBConnection() {
duckdb_disconnect(&conn_);
duckdb_close(&db_);
}
DuckDBConnection(const DuckDBConnection& _other) = delete;
DuckDBConnection(DuckDBConnection&& _other)
: conn_(_other.conn_), db_(_other.db_) {
_other.conn_ = NULL;
_other.db_ = NULL;
}
DuckDBConnection& operator=(const DuckDBConnection& _other) = delete;
DuckDBConnection& operator=(DuckDBConnection&& _other);
duckdb_connection conn() { return conn_; }
duckdb_database db() { return db_; }
private:
duckdb_connection conn_;
duckdb_database db_;
};
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,69 @@
#ifndef SQLGEN_DUCKDB_DUCKDBRESULT_HPP_
#define SQLGEN_DUCKDB_DUCKDBRESULT_HPP_
#include <duckdb.h>
#include <string>
#include "../sqlgen_api.hpp"
#include "DuckDBConnection.hpp"
namespace sqlgen::duckdb {
class SQLGEN_API DuckDBResult {
using ConnPtr = Ref<DuckDBConnection>;
public:
static Result<Ref<DuckDBResult>> make(const std::string& _query,
const ConnPtr& _conn) {
try {
return Ref<DuckDBResult>::make(_query, _conn);
} catch (const std::exception& e) {
return error(e.what());
}
}
DuckDBResult(const std::string& _query, const ConnPtr& _conn)
: destroy_(false) {
if (duckdb_query(_conn->conn(), _query.c_str(), &res_) == DuckDBError) {
throw std::runtime_error(duckdb_result_error(&res_));
}
destroy_ = true;
}
~DuckDBResult() {
if (destroy_) {
duckdb_destroy_result(&res_);
}
}
DuckDBResult(const DuckDBResult& _other) = delete;
DuckDBResult(DuckDBResult&& _other)
: destroy_(_other.destroy_), res_(_other.res_) {
_other.destroy_ = false;
}
DuckDBResult& operator=(const DuckDBResult& _other) = delete;
DuckDBResult& operator=(DuckDBResult&& _other) {
if (this == &_other) {
return *this;
}
destroy_ = _other.destroy_;
res_ = _other.res_;
_other.destroy_ = false;
return *this;
}
duckdb_result& res() { return res_; }
private:
bool destroy_;
duckdb_result res_;
};
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,113 @@
#ifndef SQLGEN_DUCKDB_ITERATOR_HPP_
#define SQLGEN_DUCKDB_ITERATOR_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <string>
#include <vector>
#include "../Ref.hpp"
#include "../Result.hpp"
#include "DuckDBConnection.hpp"
#include "DuckDBResult.hpp"
#include "from_chunk_ptrs.hpp"
#include "make_chunk_ptrs.hpp"
namespace sqlgen::duckdb {
template <class T>
class Iterator {
using ConnPtr = Ref<DuckDBConnection>;
using ResultPtr = Ref<DuckDBResult>;
public:
struct End {
bool operator==(const Iterator<T>& _it) const noexcept {
return _it == *this;
}
bool operator!=(const Iterator<T>& _it) const noexcept {
return _it != *this;
}
};
public:
using difference_type = std::ptrdiff_t;
using value_type = Result<T>;
Iterator(const std::string& _query, const ConnPtr& _conn)
: res_(DuckDBResult::make(_query, _conn)),
conn_(_conn),
current_batch_(get_next_batch(res_, _conn)),
ix_(0) {}
~Iterator() = default;
Result<T>& operator*() const noexcept { return (*current_batch_)[ix_]; }
Result<T>* operator->() const noexcept { return &(*current_batch_)[ix_]; }
bool operator==(const End&) const noexcept {
return current_batch_->size() == 0;
}
bool operator!=(const End& _end) const noexcept { return !(*this == _end); }
Iterator<T>& operator++() noexcept {
++ix_;
if (ix_ >= current_batch_->size()) {
current_batch_ = get_next_batch(res_, conn_);
ix_ = 0;
}
return *this;
}
void operator++(int) noexcept { ++*this; }
private:
static Ref<std::vector<Result<T>>> get_next_batch(
const Result<ResultPtr>& _result_ptr, const ConnPtr& _conn) noexcept {
return _result_ptr
.and_then([&](const auto& _res) -> Result<Ref<std::vector<Result<T>>>> {
duckdb_data_chunk chunk = duckdb_fetch_chunk(_res->res());
if (!chunk) {
return Ref<std::vector<Result<T>>>::make();
}
const idx_t row_count = duckdb_data_chunk_get_size(chunk);
auto res =
make_chunk_ptrs<T>(_res, chunk)
.transform([&](auto&& _chunk_ptrs) {
auto batch = Ref<std::vector<Result<T>>>::make();
for (idx_t i = 0; i < row_count; ++i) {
batch->emplace_back(from_chunk_ptrs<T>(_chunk_ptrs, i));
}
return batch;
});
duckdb_destroy_data_chunk(&chunk);
return res;
})
.or_else([](auto _err) {
return Ref<std::vector<Result<T>>>::make(
std::vector<Result<T>>({Result<T>(_err)}));
})
.value();
}
private:
/// The underlying DuckDB result.
Result<ResultPtr> res_;
/// The underlying connection.
ConnPtr conn_;
/// The current batch of results.
Ref<std::vector<Result<T>>> current_batch_;
/// The index on the current_chunk
idx_t ix_;
};
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,103 @@
#ifndef SQLGEN_DUCKDB_CASTDUCKDBTYPE_HPP_
#define SQLGEN_DUCKDB_CASTDUCKDBTYPE_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <type_traits>
#include "../Ref.hpp"
#include "../Result.hpp"
namespace sqlgen::duckdb {
template <class T, class U>
Ref<std::vector<T>> cast_as_vector(const size_t _size, U* _ptr) {
constexpr int64_t microseconds_per_day =
static_cast<int64_t>(24 * 60 * 60) * static_cast<int64_t>(1000000);
auto vec = Ref<std::vector<T>>::make(_size);
for (size_t i = 0; i < _size; ++i) {
if constexpr (std::is_same_v<U, duckdb_hugeint>) {
(*vec)[i] = static_cast<T>(duckdb_hugeint_to_double(_ptr[i]));
} else if constexpr (std::is_same_v<T, duckdb_timestamp> &&
std::is_same_v<U, duckdb_date>) {
(*vec)[i] = duckdb_timestamp{
.micros = static_cast<int64_t>(_ptr[i].days) * microseconds_per_day};
} else if constexpr (std::is_same_v<T, duckdb_date> &&
std::is_same_v<U, duckdb_timestamp>) {
(*vec)[i] = duckdb_date{
.days = static_cast<int32_t>(_ptr[i].micros / microseconds_per_day)};
} else {
(*vec)[i] = static_cast<T>(_ptr[i]);
}
}
return vec;
}
template <class T>
Result<Ref<std::vector<T>>> cast_duckdb_type(const duckdb_type _type,
const size_t _size,
void* _raw_ptr) {
if constexpr (std::is_same_v<T, duckdb_timestamp>) {
if (_type == DUCKDB_TYPE_DATE) {
return cast_as_vector<T>(_size, static_cast<duckdb_date*>(_raw_ptr));
}
return error("Could not cast");
} else if constexpr (std::is_same_v<T, duckdb_date>) {
if (_type == DUCKDB_TYPE_TIMESTAMP) {
return cast_as_vector<T>(_size, static_cast<duckdb_timestamp*>(_raw_ptr));
}
return error("Could not cast");
} else if constexpr (!std::is_floating_point_v<T> && !std::is_integral_v<T>) {
return error("Could not cast");
} else {
switch (_type) {
case DUCKDB_TYPE_TINYINT:
return cast_as_vector<T>(_size, static_cast<int8_t*>(_raw_ptr));
case DUCKDB_TYPE_UTINYINT:
return cast_as_vector<T>(_size, static_cast<uint8_t*>(_raw_ptr));
case DUCKDB_TYPE_SMALLINT:
return cast_as_vector<T>(_size, static_cast<int16_t*>(_raw_ptr));
case DUCKDB_TYPE_USMALLINT:
return cast_as_vector<T>(_size, static_cast<uint16_t*>(_raw_ptr));
case DUCKDB_TYPE_INTEGER:
return cast_as_vector<T>(_size, static_cast<int32_t*>(_raw_ptr));
case DUCKDB_TYPE_UINTEGER:
return cast_as_vector<T>(_size, static_cast<uint32_t*>(_raw_ptr));
case DUCKDB_TYPE_BIGINT:
return cast_as_vector<T>(_size, static_cast<int64_t*>(_raw_ptr));
case DUCKDB_TYPE_UBIGINT:
return cast_as_vector<T>(_size, static_cast<uint64_t*>(_raw_ptr));
case DUCKDB_TYPE_FLOAT:
return cast_as_vector<T>(_size, static_cast<float*>(_raw_ptr));
case DUCKDB_TYPE_DOUBLE:
return cast_as_vector<T>(_size, static_cast<double*>(_raw_ptr));
case DUCKDB_TYPE_HUGEINT:
return cast_as_vector<T>(_size, static_cast<duckdb_hugeint*>(_raw_ptr));
default:
return error("Could not cast");
}
}
}
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,68 @@
#ifndef SQLGEN_DUCKDB_CHECKDUCKDBTYPE_HPP_
#define SQLGEN_DUCKDB_CHECKDUCKDBTYPE_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <type_traits>
#include "../Result.hpp"
namespace sqlgen::duckdb {
template <class T>
bool check_duckdb_type(duckdb_type _t) {
using Type = std::remove_cvref_t<T>;
switch (_t) {
case DUCKDB_TYPE_BOOLEAN:
return std::is_same_v<Type, bool>;
case DUCKDB_TYPE_TINYINT:
return std::is_same_v<Type, char> || std::is_same_v<Type, int8_t>;
case DUCKDB_TYPE_ENUM:
case DUCKDB_TYPE_UTINYINT:
return std::is_same_v<Type, uint8_t>;
case DUCKDB_TYPE_SMALLINT:
return std::is_same_v<Type, int16_t>;
case DUCKDB_TYPE_USMALLINT:
return std::is_same_v<Type, uint16_t>;
case DUCKDB_TYPE_INTEGER:
return std::is_same_v<Type, int32_t>;
case DUCKDB_TYPE_UINTEGER:
return std::is_same_v<Type, uint32_t>;
case DUCKDB_TYPE_BIGINT:
return std::is_same_v<Type, int64_t>;
case DUCKDB_TYPE_UBIGINT:
return std::is_same_v<Type, uint64_t>;
case DUCKDB_TYPE_FLOAT:
return std::is_same_v<Type, float>;
case DUCKDB_TYPE_DOUBLE:
return std::is_same_v<Type, double>;
case DUCKDB_TYPE_DATE:
return std::is_same_v<Type, duckdb_date>;
case DUCKDB_TYPE_VARCHAR:
return std::is_same_v<Type, duckdb_string_t>;
case DUCKDB_TYPE_TIMESTAMP:
return std::is_same_v<Type, duckdb_timestamp>;
default:
return false;
}
}
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,32 @@
#ifndef SQLGEN_DUCKDB_CHUNKPTRST_HPP_
#define SQLGEN_DUCKDB_CHUNKPTRST_HPP_
#include <rfl.hpp>
#include <type_traits>
#include "./parsing/Parser.hpp"
#include "ColumnData.hpp"
namespace sqlgen::duckdb {
template <class T>
struct ChunkPtrsType;
template <class... FieldTs>
struct ChunkPtrsType<rfl::NamedTuple<FieldTs...>> {
using Type = rfl::Tuple<ColumnData<
typename duckdb::parsing::Parser<typename FieldTs::Type>::ResultingType,
typename FieldTs::Name>...>;
};
template <class T>
struct ChunkPtrsType {
using Type = typename ChunkPtrsType<rfl::named_tuple_t<T>>::Type;
};
template <class T>
using chunk_ptrs_t = typename ChunkPtrsType<std::remove_cvref_t<T>>::Type;
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,16 @@
#ifndef SQLGEN_DUCKDB_CONNECT_HPP_
#define SQLGEN_DUCKDB_CONNECT_HPP_
#include <string>
#include "Connection.hpp"
namespace sqlgen::duckdb {
inline auto connect(const std::string& _fname = ":memory:") {
return Connection::make(_fname);
}
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,49 @@
#ifndef SQLGEN_DUCKDB_FROMCHUNKPTRS_HPP_
#define SQLGEN_DUCKDB_FROMCHUNKPTRS_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <rfl/NamedTuple.hpp>
#include <rfl/from_named_tuple.hpp>
#include <type_traits>
#include <utility>
#include "../Result.hpp"
#include "./parsing/Parser_default.hpp"
#include "ColumnData.hpp"
#include "chunk_ptrs_t.hpp"
namespace sqlgen::duckdb {
template <class T, class NamedTupeT, class ChunkPtrsT>
struct FromChunkPtrs;
template <class T, class... FieldTs, class... Ts, class... ColNames>
struct FromChunkPtrs<T, rfl::NamedTuple<FieldTs...>,
rfl::Tuple<ColumnData<Ts, ColNames>...>> {
Result<T> operator()(
const rfl::Tuple<ColumnData<Ts, ColNames>...>& _chunk_ptrs,
idx_t _i) noexcept {
return [&]<int... _is>(std::integer_sequence<int, _is...>) -> Result<T> {
try {
return rfl::from_named_tuple<T>(rfl::named_tuple_t<T>(
duckdb::parsing::Parser<typename FieldTs::Type>::read(
rfl::get<_is>(_chunk_ptrs).is_not_null(_i)
? rfl::get<_is>(_chunk_ptrs).data + _i
: nullptr)
.value()...));
} catch (const std::exception& e) {
return error(e.what());
}
}(std::make_integer_sequence<int, sizeof...(Ts)>());
}
};
template <class T>
auto from_chunk_ptrs = FromChunkPtrs<std::remove_cvref_t<T>,
rfl::named_tuple_t<T>, chunk_ptrs_t<T>>{};
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,90 @@
#ifndef SQLGEN_DUCKDB_MAKECHUNKPTRS_HPP_
#define SQLGEN_DUCKDB_MAKECHUNKPTRS_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <rfl/enums.hpp>
#include <rfl/type_name_t.hpp>
#include <type_traits>
#include <utility>
#include "../Ref.hpp"
#include "ColumnData.hpp"
#include "DuckDBResult.hpp"
#include "cast_duckdb_type.hpp"
#include "check_duckdb_type.hpp"
#include "chunk_ptrs_t.hpp"
namespace sqlgen::duckdb {
template <class T>
struct MakeChunkPtrs;
template <class... Ts, class... ColNames>
struct MakeChunkPtrs<rfl::Tuple<ColumnData<Ts, ColNames>...>> {
Result<rfl::Tuple<ColumnData<Ts, ColNames>...>> operator()(
const Ref<DuckDBResult>& _res, duckdb_data_chunk _chunk) {
try {
return [&]<int... _is>(std::integer_sequence<int, _is...>) {
return rfl::Tuple<ColumnData<Ts, ColNames>...>(
make_column_data<Ts, ColNames, _is>(_res, _chunk)...);
}(std::make_integer_sequence<int, sizeof...(Ts)>());
} catch (const std::exception& e) {
return error(e.what());
}
}
template <class T, class ColName, int _i>
static auto make_column_data(const Ref<DuckDBResult>& _res,
duckdb_data_chunk _chunk) {
const auto actual_duckdb_type = duckdb_column_type(&_res->res(), _i);
auto vec = duckdb_data_chunk_get_vector(_chunk, _i);
if (check_duckdb_type<T>(actual_duckdb_type)) {
return ColumnData<T, ColName>{
.vec = vec,
.data = static_cast<T*>(duckdb_vector_get_data(vec)),
.validity = duckdb_vector_get_validity(vec)};
}
if constexpr (std::is_same_v<T, bool>) {
throw std::runtime_error("Wrong type in field '" + ColName().str() +
"'. " + rfl::enum_to_string(actual_duckdb_type) +
" could not be cast to " +
rfl::type_name_t<T>().str() + ".");
} else {
const auto ptr_res = cast_duckdb_type<T>(
actual_duckdb_type, duckdb_data_chunk_get_size(_chunk),
duckdb_vector_get_data(vec));
if (!ptr_res) {
throw std::runtime_error(
"Wrong type in field '" + ColName().str() + "'. " +
rfl::enum_to_string(actual_duckdb_type) + " could not be cast to " +
rfl::type_name_t<T>().str() + ".");
}
return ColumnData<T, ColName>{.vec = vec,
.data = (*ptr_res)->data(),
.validity = duckdb_vector_get_validity(vec),
.ptr = ptr_res->ptr()};
}
}
};
template <class T>
struct MakeChunkPtrs {
auto operator()(const Ref<DuckDBResult>& _res, duckdb_data_chunk _chunk) {
return MakeChunkPtrs<chunk_ptrs_t<T>>{}(_res, _chunk);
}
};
template <class T>
auto make_chunk_ptrs = MakeChunkPtrs<std::remove_cvref_t<T>>{};
} // namespace sqlgen::duckdb
#endif

View File

@@ -0,0 +1,15 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_HPP_
#include "Parser_base.hpp"
#include "Parser_date.hpp"
#include "Parser_default.hpp"
#include "Parser_enum.hpp"
#include "Parser_json.hpp"
#include "Parser_optional.hpp"
#include "Parser_reflection_type.hpp"
#include "Parser_smart_ptr.hpp"
#include "Parser_string.hpp"
#include "Parser_timestamp.hpp"
#endif

View File

@@ -0,0 +1,11 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_BASE_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_BASE_HPP_
namespace sqlgen::duckdb::parsing {
template <class T>
struct Parser;
}
#endif

View File

@@ -0,0 +1,42 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_DATE_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_DATE_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <rfl/internal/StringLiteral.hpp>
#include <string>
#include "../../Result.hpp"
#include "../../Timestamp.hpp"
#include "Parser_base.hpp"
namespace sqlgen::duckdb::parsing {
template <>
struct Parser<Date> {
using ResultingType = duckdb_date;
static constexpr time_t seconds_per_day = 24 * 60 * 60;
static Result<Date> read(const ResultingType* _r) noexcept {
if (!_r) {
return error("Date value cannot be NULL.");
}
return Date(static_cast<time_t>(_r->days) * seconds_per_day);
}
static Result<Nothing> write(const Date& _t,
duckdb_appender _appender) noexcept {
return duckdb_append_date(
_appender, duckdb_date{.days = static_cast<int32_t>(
_t.to_time_t() / seconds_per_day)}) !=
DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append date value."));
}
};
} // namespace sqlgen::duckdb::parsing
#endif

View File

@@ -0,0 +1,94 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_DEFAULT_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_DEFAULT_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <string>
#include <type_traits>
#include "../../Result.hpp"
#include "Parser_base.hpp"
namespace sqlgen::duckdb::parsing {
template <class T>
struct Parser {
using Type = std::remove_cvref_t<T>;
using ResultingType = Type;
static Result<T> read(const ResultingType* _r) noexcept {
if (!_r) {
return error("Numeric or boolean value cannot be NULL.");
}
return Type(*_r);
}
static Result<Nothing> write(const T& _t,
duckdb_appender _appender) noexcept {
if constexpr (std::is_same_v<Type, bool>) {
return duckdb_append_bool(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append boolean value."));
} else if constexpr (std::is_same_v<Type, char> ||
std::is_same_v<Type, int8_t>) {
return duckdb_append_int8(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append int8 value."));
} else if constexpr (std::is_same_v<Type, uint8_t>) {
return duckdb_append_uint8(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append uint8 value."));
} else if constexpr (std::is_same_v<Type, int16_t>) {
return duckdb_append_int16(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append int16 value."));
} else if constexpr (std::is_same_v<Type, uint16_t>) {
return duckdb_append_uint16(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append uint16 value."));
} else if constexpr (std::is_same_v<Type, int32_t>) {
return duckdb_append_int32(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append int32 value."));
} else if constexpr (std::is_same_v<Type, uint32_t>) {
return duckdb_append_uint32(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append uint32 value."));
} else if constexpr (std::is_same_v<Type, int64_t>) {
return duckdb_append_int64(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append int64 value."));
} else if constexpr (std::is_same_v<Type, uint64_t>) {
return duckdb_append_uint64(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append uint64 value."));
} else if constexpr (std::is_same_v<Type, float>) {
return duckdb_append_float(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append float value."));
} else if constexpr (std::is_same_v<Type, double>) {
return duckdb_append_double(_appender, _t) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append double value."));
} else {
static_assert(rfl::always_false_v<T>, "Unsupported type.");
return error("Unsupported type.");
}
}
};
} // namespace sqlgen::duckdb::parsing
#endif

View File

@@ -0,0 +1,44 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_ENUM_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_ENUM_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <rfl/enums.hpp>
#include <string>
#include <type_traits>
#include "../../Result.hpp"
#include "Parser_base.hpp"
namespace sqlgen::duckdb::parsing {
template <class EnumT>
requires std::is_enum_v<EnumT>
struct Parser<EnumT> {
using ResultingType = uint8_t;
static_assert(enchantum::ScopedEnum<EnumT>, "The enum must be scoped.");
static constexpr auto arr = rfl::get_enumerator_array<EnumT>();
static_assert(arr.size() < 255, "Enum size cannot exceed 255.");
static Result<EnumT> read(const ResultingType* _r) noexcept {
if (!_r) {
return error("Enum value cannot be NULL.");
}
return static_cast<EnumT>(*_r);
}
static Result<Nothing> write(const EnumT& _t,
duckdb_appender _appender) noexcept {
const auto str = rfl::enum_to_string(_t);
return duckdb_append_varchar_length(_appender, str.c_str(), str.length()) !=
DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append string value."));
}
};
} // namespace sqlgen::duckdb::parsing
#endif

View File

@@ -0,0 +1,39 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_JSON_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_JSON_HPP_
#include <duckdb.h>
#include <rfl/json.hpp>
#include <string>
#include <type_traits>
#include "../../JSON.hpp"
#include "../../Result.hpp"
#include "Parser_base.hpp"
#include "Parser_string.hpp"
namespace sqlgen::duckdb::parsing {
template <class T>
struct Parser<JSON<T>> {
using ResultingType = duckdb_string_t;
static Result<JSON<T>> read(const ResultingType* _r) noexcept {
return Parser<std::string>::read(_r).and_then(
[&](const auto& _str) { return rfl::json::read<T>(_str); });
}
static Result<Nothing> write(const JSON<T>& _t,
duckdb_appender _appender) noexcept {
try {
return Parser<std::string>::write(rfl::json::write(_t.value()),
_appender);
} catch (const std::exception& e) {
return error(e.what());
}
}
};
} // namespace sqlgen::duckdb::parsing
#endif

View File

@@ -0,0 +1,43 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_OPTIONAL_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_OPTIONAL_HPP_
#include <duckdb.h>
#include <optional>
#include <string>
#include <type_traits>
#include "../../Result.hpp"
#include "Parser_base.hpp"
namespace sqlgen::duckdb::parsing {
template <class T>
struct Parser<std::optional<T>> {
using Type = std::remove_cvref_t<T>;
using ResultingType = typename Parser<Type>::ResultingType;
static Result<std::optional<T>> read(const ResultingType* _r) noexcept {
if (!_r) {
return std::optional<T>();
}
return Parser<std::remove_cvref_t<T>>::read(_r).transform(
[](auto&& _t) -> std::optional<T> {
return std::make_optional<T>(std::move(_t));
});
}
static Result<Nothing> write(const std::optional<T>& _o,
duckdb_appender _appender) noexcept {
if (!_o) {
return duckdb_append_null(_appender) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append null value."));
}
return Parser<std::remove_cvref_t<T>>::write(*_o, _appender);
}
};
} // namespace sqlgen::duckdb::parsing
#endif

View File

@@ -0,0 +1,36 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_FOREIGN_KEY_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_FOREIGN_KEY_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <type_traits>
#include "../../Result.hpp"
#include "../../transpilation/has_reflection_method.hpp"
#include "Parser_base.hpp"
namespace sqlgen::duckdb::parsing {
template <class T>
requires transpilation::has_reflection_method<std::remove_cvref_t<T>>
struct Parser<T> {
using Type = std::remove_cvref_t<T>;
using ResultingType = typename Parser<
std::remove_cvref_t<typename Type::ReflectionType>>::ResultingType;
static Result<T> read(const ResultingType* _r) noexcept {
return Parser<std::remove_cvref_t<typename Type::ReflectionType>>::read(_r)
.transform([](auto&& _t) { return T(std::move(_t)); });
}
static Result<Nothing> write(const T& _t,
duckdb_appender _appender) noexcept {
return Parser<std::remove_cvref_t<typename Type::ReflectionType>>::write(
_t.reflection(), _appender);
}
};
} // namespace sqlgen::duckdb::parsing
#endif

View File

@@ -0,0 +1,46 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_SMART_PTR_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_SMART_PTR_HPP_
#include <duckdb.h>
#include <string>
#include <type_traits>
#include "../../Result.hpp"
#include "../../transpilation/is_nullable.hpp"
#include "Parser_base.hpp"
namespace sqlgen::duckdb::parsing {
template <class T>
requires transpilation::is_nullable_v<std::remove_cvref_t<T>>
struct Parser<T> {
using Type = std::remove_cvref_t<T>;
using ResultingType =
typename Parser<typename Type::value_type>::ResultingType;
static Result<T> read(const ResultingType* _r) noexcept {
if (!_r) {
return T();
}
return Parser<typename Type::value_type>::read(_r).transform(
[](auto&& _u) -> T {
using U = std::remove_cvref<decltype(_u)>;
return T(new U(std::move(_u)));
});
}
static Result<Nothing> write(const T& _ptr,
duckdb_appender _appender) noexcept {
if (!_ptr) {
return duckdb_append_null(_appender) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append null value."));
}
return Parser<std::remove_cvref_t<T>>::write(*_ptr, _appender);
}
};
} // namespace sqlgen::duckdb::parsing
#endif

View File

@@ -0,0 +1,40 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_STRING_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_STRING_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <string>
#include "../../Result.hpp"
#include "Parser_base.hpp"
namespace sqlgen::duckdb::parsing {
template <>
struct Parser<std::string> {
using ResultingType = duckdb_string_t;
static Result<std::string> read(const ResultingType* _r) noexcept {
if (!_r) {
return error("String value cannot be NULL.");
}
if (duckdb_string_is_inlined(*_r)) {
return std::string(_r->value.inlined.inlined, _r->value.inlined.length);
} else {
return std::string(_r->value.pointer.ptr, _r->value.pointer.length);
}
}
static Result<Nothing> write(const std::string& _t,
duckdb_appender _appender) noexcept {
return duckdb_append_varchar_length(_appender, _t.c_str(), _t.length()) !=
DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append string value."));
}
};
} // namespace sqlgen::duckdb::parsing
#endif

View File

@@ -0,0 +1,40 @@
#ifndef SQLGEN_DUCKDB_PARSING_PARSER_TIMESTAMP_HPP_
#define SQLGEN_DUCKDB_PARSING_PARSER_TIMESTAMP_HPP_
#include <duckdb.h>
#include <rfl.hpp>
#include <rfl/internal/StringLiteral.hpp>
#include <string>
#include "../../Result.hpp"
#include "Parser_base.hpp"
namespace sqlgen::duckdb::parsing {
template <rfl::internal::StringLiteral _format>
struct Parser<rfl::Timestamp<_format>> {
using ResultingType = duckdb_timestamp;
static Result<rfl::Timestamp<_format>> read(
const ResultingType* _r) noexcept {
if (!_r) {
return error("Timestamp value cannot be NULL.");
}
return rfl::Timestamp<_format>(static_cast<time_t>(_r->micros / 1000000));
}
static Result<Nothing> write(const rfl::Timestamp<_format>& _t,
duckdb_appender _appender) noexcept {
return duckdb_append_timestamp(
_appender,
duckdb_timestamp{.micros = static_cast<int64_t>(_t.to_time_t()) *
1000000}) != DuckDBError
? Result<Nothing>(Nothing{})
: Result<Nothing>(error("Could not append timestamp value."));
}
};
} // namespace sqlgen::duckdb::parsing
#endif

View File

@@ -0,0 +1,27 @@
#ifndef SQLGEN_DUCKDB_TO_SQL_HPP_
#define SQLGEN_DUCKDB_TO_SQL_HPP_
#include <string>
#include "../dynamic/Statement.hpp"
#include "../sqlgen_api.hpp"
#include "../transpilation/to_sql.hpp"
namespace sqlgen::duckdb {
/// Transpiles a dynamic general SQL statement to the duckdb dialect.
SQLGEN_API std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept;
/// Transpiles any SQL statement to the duckdb dialect.
template <class T>
std::string to_sql(const T& _t) noexcept {
if constexpr (std::is_same_v<std::remove_cvref_t<T>, dynamic::Statement>) {
return to_sql_impl(_t);
} else {
return to_sql_impl(transpilation::to_sql(_t));
}
}
} // namespace sqlgen::duckdb
#endif

View File

@@ -12,11 +12,12 @@ namespace sqlgen::internal {
template <class ContainerType, class IteratorType>
auto to_container(const Result<IteratorType>& _res) {
if constexpr (internal::is_range_v<ContainerType>) {
return _res.transform([](auto&& _it) { return Range<IteratorType>(_it); });
return _res.transform(
[](auto&& _it) { return Range<IteratorType>(std::move(_it)); });
} else {
return to_container<Range<IteratorType>>(_res).and_then(
[](auto range) -> Result<ContainerType> {
[](const auto& range) -> Result<ContainerType> {
ContainerType container;
for (auto& res : range) {
if (res) {

View File

@@ -15,6 +15,7 @@
#include "../dynamic/Column.hpp"
#include "../dynamic/Statement.hpp"
#include "../dynamic/Write.hpp"
#include "../internal/iterator_t.hpp"
#include "../internal/to_container.hpp"
#include "../internal/write_or_insert.hpp"
#include "../is_connection.hpp"

View File

@@ -0,0 +1,40 @@
#include "sqlgen/duckdb/Connection.hpp"
#include <ranges>
#include <rfl.hpp>
#include <sstream>
#include <stdexcept>
// #include "sqlgen/duckdb/Iterator.hpp"
#include "sqlgen/internal/collect/vector.hpp"
#include "sqlgen/internal/strings/strings.hpp"
namespace sqlgen::duckdb {
Result<Nothing> Connection::begin_transaction() noexcept {
return execute("BEGIN TRANSACTION;");
}
Result<Nothing> Connection::commit() noexcept { return execute("COMMIT;"); }
Result<Nothing> Connection::execute(const std::string& _sql) noexcept {
duckdb_result res{};
const auto state = duckdb_query(conn_->conn(), _sql.c_str(), &res);
if (state == DuckDBError) {
const auto err = error(duckdb_result_error(&res));
duckdb_destroy_result(&res);
return err;
}
duckdb_destroy_result(&res);
return Nothing{};
}
rfl::Result<Ref<Connection>> Connection::make(
const std::optional<std::string>& _fname) noexcept {
return DuckDBConnection::make(_fname).transform(
[](auto&& _conn) { return Ref<Connection>::make(std::move(_conn)); });
}
Result<Nothing> Connection::rollback() noexcept { return execute("ROLLBACK;"); }
} // namespace sqlgen::duckdb

View File

@@ -0,0 +1,43 @@
#include "sqlgen/duckdb/DuckDBConnection.hpp"
namespace sqlgen::duckdb {
Result<Ref<DuckDBConnection>> DuckDBConnection::make(
const std::optional<std::string>& _fname) {
duckdb_database db = NULL;
const auto res_db =
_fname ? duckdb_open(_fname->c_str(), &db) : duckdb_open(NULL, &db);
if (res_db == DuckDBError) {
duckdb_close(&db);
return error("Could not open database.");
}
duckdb_connection conn = NULL;
const auto res_conn = duckdb_connect(db, &conn);
if (res_conn == DuckDBError) {
duckdb_disconnect(&conn);
duckdb_close(&db);
return error("Could not connect to database.");
}
return Ref<DuckDBConnection>::make(conn, db);
}
DuckDBConnection& DuckDBConnection::operator=(DuckDBConnection&& _other) {
if (this == &_other) {
return *this;
}
duckdb_disconnect(&conn_);
duckdb_close(&db_);
conn_ = _other.conn_;
db_ = _other.db_;
_other.conn_ = NULL;
_other.db_ = NULL;
return *this;
}
} // namespace sqlgen::duckdb

View File

@@ -0,0 +1,27 @@
#include "sqlgen/postgres/exec.hpp"
#include <ranges>
#include <rfl.hpp>
#include <sstream>
#include <stdexcept>
namespace sqlgen::postgres {
Result<Ref<PGresult>> exec(const Ref<PGconn>& _conn,
const std::string& _sql) noexcept {
const auto res = PQexec(_conn.get(), _sql.c_str());
const auto status = PQresultStatus(res);
if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK &&
status != PGRES_COPY_IN) {
const auto err =
error("Executing '" + _sql + "' failed: " + PQresultErrorMessage(res));
PQclear(res);
return err;
}
return Ref<PGresult>::make(std::shared_ptr<PGresult>(res, PQclear));
}
} // namespace sqlgen::postgres

View File

@@ -0,0 +1,960 @@
#include "sqlgen/duckdb/to_sql.hpp"
#include <functional>
#include <ranges>
#include <rfl.hpp>
#include <sstream>
#include <stdexcept>
#include <type_traits>
#include "sqlgen/dynamic/Column.hpp"
#include "sqlgen/dynamic/Join.hpp"
#include "sqlgen/dynamic/Operation.hpp"
#include "sqlgen/internal/collect/vector.hpp"
#include "sqlgen/internal/strings/strings.hpp"
namespace sqlgen::duckdb {
std::string aggregation_to_sql(
const dynamic::Aggregation& _aggregation) noexcept;
std::string column_or_value_to_sql(const dynamic::ColumnOrValue& _col) noexcept;
std::string condition_to_sql(const dynamic::Condition& _cond) noexcept;
template <class ConditionType>
std::string condition_to_sql_impl(const ConditionType& _condition) noexcept;
std::string column_to_sql_definition(const dynamic::Table& _table,
const dynamic::Column& _col) noexcept;
std::string create_enums(const dynamic::CreateTable& _stmt) noexcept;
std::string create_index_to_sql(const dynamic::CreateIndex& _stmt) noexcept;
std::string create_sequences_for_auto_incr(
const dynamic::CreateTable& _stmt) noexcept;
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept;
std::string create_as_to_sql(const dynamic::CreateAs& _stmt) noexcept;
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::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept;
std::vector<std::string> get_primary_keys(
const dynamic::CreateTable& _stmt) noexcept;
std::vector<std::pair<std::string, std::vector<std::string>>> get_enum_types(
const dynamic::CreateTable& _stmt) noexcept;
std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept;
std::string join_to_sql(const dynamic::Join& _stmt) noexcept;
std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept;
std::string make_sequence_name(const dynamic::Table& _table,
const dynamic::Column& _col) noexcept;
std::string properties_to_sql(const dynamic::Table& _table,
const dynamic::Column& _col) noexcept;
std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept;
std::string table_or_query_to_sql(
const dynamic::SelectFrom::TableOrQueryType& _table_or_query) noexcept;
std::string type_to_sql(const dynamic::Type& _type) noexcept;
std::string update_to_sql(const dynamic::Update& _stmt) noexcept;
std::string write_to_sql(const dynamic::Write& _stmt) noexcept;
// ----------------------------------------------------------------------------
inline std::string get_name(const dynamic::Column& _col) { return _col.name; }
inline std::pair<std::string, std::vector<std::string>> get_enum_mapping(
const dynamic::Column& _col) {
return _col.type.visit(
[&](const auto& _t) -> std::pair<std::string, std::vector<std::string>> {
using T = std::remove_cvref_t<decltype(_t)>;
if constexpr (std::is_same_v<T, dynamic::types::Enum>) {
return {type_to_sql(_t), _t.values};
}
return {};
});
}
inline std::string wrap_in_quotes(const std::string& _name) noexcept {
return "\"" + _name + "\"";
}
inline std::string wrap_in_single_quotes(const std::string& _name) noexcept {
return "'" + _name + "'";
}
// ----------------------------------------------------------------------------
std::string aggregation_to_sql(
const dynamic::Aggregation& _aggregation) noexcept {
return _aggregation.val.visit([](const auto& _agg) -> std::string {
using Type = std::remove_cvref_t<decltype(_agg)>;
std::stringstream stream;
if constexpr (std::is_same_v<Type, dynamic::Aggregation::Avg>) {
stream << "AVG(" << operation_to_sql(*_agg.val) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Count>) {
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(" << operation_to_sql(*_agg.val) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Min>) {
stream << "MIN(" << operation_to_sql(*_agg.val) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Sum>) {
stream << "SUM(" << operation_to_sql(*_agg.val) << ")";
} else {
static_assert(rfl::always_false_v<Type>, "Not all cases were covered.");
}
return stream.str();
});
}
std::string column_or_value_to_sql(
const dynamic::ColumnOrValue& _col) noexcept {
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 "'" + escape_single_quote(_v.val) + "'";
} else if constexpr (std::is_same_v<Type, dynamic::Duration>) {
return "INTERVAL '" + std::to_string(_v.val) + " " +
rfl::enum_to_string(_v.unit) + "'";
} else if constexpr (std::is_same_v<Type, dynamic::Timestamp>) {
return "to_timestamp(" + std::to_string(_v.seconds_since_unix) + ")";
} else if constexpr (std::is_same_v<Type, dynamic::Boolean>) {
return _v.val ? "TRUE" : "FALSE";
} else {
return std::to_string(_v.val);
}
};
return _col.visit([&](const auto& _c) -> std::string {
using Type = std::remove_cvref_t<decltype(_c)>;
if constexpr (std::is_same_v<Type, dynamic::Column>) {
if (_c.alias) {
return *_c.alias + "." + wrap_in_quotes(_c.name);
} else {
return wrap_in_quotes(_c.name);
}
} else {
return _c.val.visit(handle_value);
}
});
}
std::string condition_to_sql(const dynamic::Condition& _cond) noexcept {
return _cond.val.visit(
[&](const auto& _c) { return condition_to_sql_impl(_c); });
}
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;
if constexpr (std::is_same_v<C, dynamic::Condition::And>) {
stream << "(" << condition_to_sql(*_condition.cond1) << ") AND ("
<< condition_to_sql(*_condition.cond2) << ")";
} else if constexpr (std::is_same_v<
C, dynamic::Condition::BooleanColumnOrValue>) {
stream << column_or_value_to_sql(_condition.col_or_val);
} else if constexpr (std::is_same_v<C, dynamic::Condition::Equal>) {
stream << operation_to_sql(_condition.op1) << " = "
<< operation_to_sql(_condition.op2);
} else if constexpr (std::is_same_v<C, dynamic::Condition::GreaterEqual>) {
stream << operation_to_sql(_condition.op1)
<< " >= " << operation_to_sql(_condition.op2);
} else if constexpr (std::is_same_v<C, dynamic::Condition::GreaterThan>) {
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";
} else if constexpr (std::is_same_v<C, dynamic::Condition::IsNotNull>) {
stream << operation_to_sql(_condition.op) << " IS NOT NULL";
} else if constexpr (std::is_same_v<C, dynamic::Condition::LesserEqual>) {
stream << operation_to_sql(_condition.op1)
<< " <= " << operation_to_sql(_condition.op2);
} else if constexpr (std::is_same_v<C, dynamic::Condition::LesserThan>) {
stream << operation_to_sql(_condition.op1) << " < "
<< operation_to_sql(_condition.op2);
} else if constexpr (std::is_same_v<C, dynamic::Condition::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 << operation_to_sql(_condition.op1)
<< " != " << operation_to_sql(_condition.op2);
} else if constexpr (std::is_same_v<C, dynamic::Condition::NotLike>) {
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::NotIn>) {
stream << operation_to_sql(_condition.op) << " NOT 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::Or>) {
stream << "(" << condition_to_sql(*_condition.cond1) << ") OR ("
<< condition_to_sql(*_condition.cond2) << ")";
} else {
static_assert(rfl::always_false_v<C>, "Not all cases were covered.");
}
return stream.str();
}
std::string column_to_sql_definition(const dynamic::Table& _table,
const dynamic::Column& _col) noexcept {
return wrap_in_quotes(_col.name) + " " + type_to_sql(_col.type) +
properties_to_sql(_table, _col);
}
std::string create_index_to_sql(const dynamic::CreateIndex& _stmt) noexcept {
using namespace std::ranges::views;
std::stringstream stream;
if (_stmt.unique) {
stream << "CREATE UNIQUE INDEX ";
} else {
stream << "CREATE INDEX ";
}
if (_stmt.if_not_exists) {
stream << "IF NOT EXISTS ";
}
stream << "\"" << _stmt.name << "\" ";
stream << "ON ";
stream << table_or_query_to_sql(_stmt.table);
stream << "(";
stream << internal::strings::join(
", ",
internal::collect::vector(_stmt.columns | transform(wrap_in_quotes)));
stream << ")";
if (_stmt.where) {
stream << " WHERE " << condition_to_sql(*_stmt.where);
}
stream << ";";
return stream.str();
}
std::string make_sequence_name(const dynamic::Table& _table,
const dynamic::Column& _col) noexcept {
return "sqlgen_seq_" + (_table.alias ? *_table.alias + "_" : std::string()) +
_table.name + "_" + _col.name;
}
std::string create_sequences_for_auto_incr(
const dynamic::CreateTable& _stmt) noexcept {
using namespace std::ranges::views;
const auto is_auto_incr = [](const auto& _col) {
return _col.type.visit(
[](const auto& _t) { return _t.properties.auto_incr; });
};
const auto create_one_sequence =
[&](const dynamic::Column& _col) -> std::string {
std::stringstream stream;
stream << "CREATE SEQUENCE ";
if (_stmt.if_not_exists) {
stream << "IF NOT EXISTS ";
}
stream << wrap_in_quotes(make_sequence_name(_stmt.table, _col)) << ";";
return stream.str();
};
return internal::strings::join(
" ", internal::collect::vector(_stmt.columns | filter(is_auto_incr) |
transform(create_one_sequence)));
}
std::string create_enums(const dynamic::CreateTable& _stmt) noexcept {
using namespace std::ranges::views;
std::stringstream stream;
for (const auto& [enum_name, enum_values] : get_enum_types(_stmt)) {
stream << "CREATE TYPE ";
if (_stmt.if_not_exists) {
stream << "IF NOT EXISTS ";
}
stream << enum_name << " AS ENUM ("
<< internal::strings::join(
", ", internal::collect::vector(
enum_values | transform(wrap_in_single_quotes)))
<< "); ";
}
return stream.str();
}
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept {
using namespace std::ranges::views;
std::stringstream stream;
stream << create_enums(_stmt);
stream << create_sequences_for_auto_incr(_stmt);
stream << "CREATE TABLE ";
if (_stmt.if_not_exists) {
stream << "IF NOT EXISTS ";
}
stream << table_or_query_to_sql(_stmt.table);
stream << "(";
stream << internal::strings::join(
", ", internal::collect::vector(
_stmt.columns | transform(std::bind_front(
column_to_sql_definition, _stmt.table))));
const auto primary_keys = get_primary_keys(_stmt);
if (primary_keys.size() != 0) {
stream << ", PRIMARY KEY (" << internal::strings::join(", ", primary_keys)
<< ")";
}
stream << ");";
return stream.str();
}
std::string create_as_to_sql(const dynamic::CreateAs& _stmt) noexcept {
std::stringstream stream;
stream << "CREATE ";
if (_stmt.or_replace) {
stream << "OR REPLACE ";
}
stream << internal::strings::replace_all(
internal::strings::to_upper(rfl::enum_to_string(_stmt.what)),
"_", " ")
<< " ";
if (_stmt.if_not_exists) {
stream << "IF NOT EXISTS ";
}
if (_stmt.table_or_view.schema) {
stream << wrap_in_quotes(*_stmt.table_or_view.schema) << ".";
}
stream << wrap_in_quotes(_stmt.table_or_view.name) << " AS ";
stream << select_from_to_sql(_stmt.query);
return stream.str();
}
std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept {
std::stringstream stream;
stream << "DELETE FROM ";
if (_stmt.table.schema) {
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
}
stream << wrap_in_quotes(_stmt.table.name);
if (_stmt.where) {
stream << " WHERE " << condition_to_sql(*_stmt.where);
}
stream << ";";
return stream.str();
}
std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept {
std::stringstream stream;
stream << "DROP "
<< internal::strings::replace_all(
internal::strings::to_upper(rfl::enum_to_string(_stmt.what)),
"_", " ")
<< " ";
if (_stmt.if_exists) {
stream << "IF EXISTS ";
}
if (_stmt.table.schema) {
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
}
stream << wrap_in_quotes(_stmt.table.name);
if (_stmt.cascade) {
stream << " CASCADE";
}
stream << ";";
return stream.str();
}
std::string escape_single_quote(const std::string& _str) noexcept {
return internal::strings::replace_all(_str, "'", "''");
}
std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept {
std::stringstream stream;
stream << operation_to_sql(_field.val);
if (_field.as) {
stream << " AS " << wrap_in_quotes(*_field.as);
}
return stream.str();
}
std::vector<std::string> get_primary_keys(
const dynamic::CreateTable& _stmt) noexcept {
using namespace std::ranges::views;
const auto is_primary_key = [](const auto& _col) -> bool {
return _col.type.visit(
[](const auto& _t) -> bool { return _t.properties.primary; });
};
return internal::collect::vector(_stmt.columns | filter(is_primary_key) |
transform(get_name) |
transform(wrap_in_quotes));
}
std::vector<std::pair<std::string, std::vector<std::string>>> get_enum_types(
const dynamic::CreateTable& _stmt) noexcept {
using namespace std::ranges::views;
const auto is_enum = [](const dynamic::Column& _col) -> bool {
return _col.type.visit([&](const auto& _t) -> bool {
using T = std::remove_cvref_t<decltype(_t)>;
return std::is_same_v<T, dynamic::types::Enum>;
});
};
return internal::collect::vector(_stmt.columns | filter(is_enum) |
transform(get_enum_mapping));
}
std::string insert_to_sql(const dynamic::Insert& _stmt) noexcept {
using namespace std::ranges::views;
std::stringstream stream;
stream << "INSERT ";
if (_stmt.or_replace) {
stream << "OR REPLACE ";
}
stream << "INTO ";
if (_stmt.table.schema) {
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
}
stream << wrap_in_quotes(_stmt.table.name);
stream << " BY NAME ( SELECT ";
stream << internal::strings::join(
", ", internal::collect::vector(
_stmt.columns | transform([&](const auto _name) {
return wrap_in_quotes(_name) + " AS " + wrap_in_quotes(_name);
})));
stream << " FROM sqlgen_appended_data)";
stream << ";";
return stream.str();
}
std::string join_to_sql(const dynamic::Join& _stmt) noexcept {
std::stringstream stream;
stream << internal::strings::to_upper(internal::strings::replace_all(
rfl::enum_to_string(_stmt.how), "_", " "))
<< " " << table_or_query_to_sql(_stmt.table_or_query) << " "
<< _stmt.alias << " ";
if (_stmt.on) {
stream << "ON " << condition_to_sql(*_stmt.on);
} else {
stream << "ON 1 = 1";
}
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::DatePlusDuration>) {
stream << operation_to_sql(*_s.date) << " + "
<< internal::strings::join(
" + ",
internal::collect::vector(
_s.durations | transform([](const auto& _d) {
return column_or_value_to_sql(dynamic::Value{_d});
})));
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Day>) {
stream << "extract(DAY from " << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type,
dynamic::Operation::DaysBetween>) {
stream << "cast(" << operation_to_sql(*_s.op2) << " as DATE) - cast("
<< operation_to_sql(*_s.op1) << " as DATE)";
} 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::Hour>) {
stream << "extract(HOUR from " << 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::Minute>) {
stream << "extract(MINUTE from " << operation_to_sql(*_s.op1) << ")";
} 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::Month>) {
stream << "extract(MONTH from " << operation_to_sql(*_s.op1) << ")";
} 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::Second>) {
stream << "extract(SECOND from " << operation_to_sql(*_s.op1) << ")";
} 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::Unixepoch>) {
stream << "extract(EPOCH FROM " << operation_to_sql(*_s.op1) << ")";
} 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 if constexpr (std::is_same_v<Type, dynamic::Operation::Weekday>) {
stream << "extract(DOW from " << operation_to_sql(*_s.op1) << ")";
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Year>) {
stream << "extract(YEAR from " << operation_to_sql(*_s.op1) << ")";
} else {
static_assert(rfl::always_false_v<Type>, "Unsupported type.");
}
return stream.str();
});
}
std::string properties_to_sql(const dynamic::Table& _table,
const dynamic::Column& _col) noexcept {
const auto properties =
_col.type.visit([](const auto& _t) { return _t.properties; });
return [&]() -> std::string {
return std::string(properties.nullable ? "" : " NOT NULL") +
std::string(properties.auto_incr
? " DEFAULT nextval('" +
make_sequence_name(_table, _col) + "')"
: "") +
std::string(properties.unique ? " UNIQUE" : "");
}() + [&]() -> std::string {
if (!properties.foreign_key_reference) {
return "";
}
const auto& ref = *properties.foreign_key_reference;
return " REFERENCES " + wrap_in_quotes(ref.table) + "(" +
wrap_in_quotes(ref.column) + ")";
}();
}
std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept {
using namespace std::ranges::views;
const auto order_by_to_str = [](const auto& _w) -> std::string {
return column_or_value_to_sql(_w.column) + (_w.desc ? " DESC" : "");
};
std::stringstream stream;
stream << "SELECT ";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.fields | transform(field_to_str)));
stream << " FROM " << table_or_query_to_sql(_stmt.table_or_query);
if (_stmt.alias) {
stream << " " << *_stmt.alias;
}
if (_stmt.joins) {
stream << " "
<< internal::strings::join(
" ", internal::collect::vector(*_stmt.joins |
transform(join_to_sql)));
}
if (_stmt.where) {
stream << " WHERE " << condition_to_sql(*_stmt.where);
}
if (_stmt.group_by) {
stream << " GROUP BY "
<< internal::strings::join(
", ",
internal::collect::vector(_stmt.group_by->columns |
transform(column_or_value_to_sql)));
}
if (_stmt.order_by) {
stream << " ORDER BY "
<< internal::strings::join(
", ", internal::collect::vector(_stmt.order_by->columns |
transform(order_by_to_str)));
}
if (_stmt.limit) {
stream << " LIMIT " << _stmt.limit->val;
}
return stream.str();
}
std::string table_or_query_to_sql(
const dynamic::SelectFrom::TableOrQueryType& _table_or_query) noexcept {
return _table_or_query.visit([](const auto& _t) -> std::string {
using Type = std::remove_cvref_t<decltype(_t)>;
if constexpr (std::is_same_v<Type, dynamic::Table>) {
if (_t.schema) {
return wrap_in_quotes(*_t.schema) + "." + wrap_in_quotes(_t.name);
}
return wrap_in_quotes(_t.name);
} else {
return "(" + select_from_to_sql(*_t) + ")";
}
});
}
std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept {
return _stmt.visit([&](const auto& _s) -> std::string {
using S = std::remove_cvref_t<decltype(_s)>;
if constexpr (std::is_same_v<S, dynamic::CreateIndex>) {
return create_index_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
return create_table_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::CreateAs>) {
return create_as_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::DeleteFrom>) {
return delete_from_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::Drop>) {
return drop_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::Insert>) {
return insert_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::SelectFrom>) {
return select_from_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::Update>) {
return update_to_sql(_s);
} else if constexpr (std::is_same_v<S, dynamic::Write>) {
return write_to_sql(_s);
} else {
static_assert(rfl::always_false_v<S>, "Unsupported type.");
}
});
}
std::string type_to_sql(const dynamic::Type& _type) noexcept {
return _type.visit([](const auto _t) -> std::string {
using T = std::remove_cvref_t<decltype(_t)>;
if constexpr (std::is_same_v<T, dynamic::types::Boolean>) {
return "BOOLEAN";
} else if constexpr (std::is_same_v<T, dynamic::types::Dynamic>) {
return _t.type_name;
} else if constexpr (std::is_same_v<T, dynamic::types::Int8>) {
return "TINYINT";
} else if constexpr (std::is_same_v<T, dynamic::types::UInt8>) {
return "UTINYINT";
} else if constexpr (std::is_same_v<T, dynamic::types::Int16>) {
return "SMALLINT";
} else if constexpr (std::is_same_v<T, dynamic::types::UInt16>) {
return "USMALLINT";
} else if constexpr (std::is_same_v<T, dynamic::types::Int32>) {
return "INTEGER";
} else if constexpr (std::is_same_v<T, dynamic::types::UInt32>) {
return "UINTEGER";
} else if constexpr (std::is_same_v<T, dynamic::types::Int64>) {
return "BIGINT";
} else if constexpr (std::is_same_v<T, dynamic::types::UInt64>) {
return "UBIGINT";
} else if constexpr (std::is_same_v<T, dynamic::types::Enum>) {
return _t.name;
} else if constexpr (std::is_same_v<T, dynamic::types::Float32>) {
return "FLOAT";
} else if constexpr (std::is_same_v<T, dynamic::types::Float64>) {
return "DOUBLE";
} else if constexpr (std::is_same_v<T, dynamic::types::Text>) {
return "TEXT";
} else if constexpr (std::is_same_v<T, dynamic::types::VarChar>) {
return "VARCHAR(" + std::to_string(_t.length) + ")";
} else if constexpr (std::is_same_v<T, dynamic::types::JSON>) {
return "JSON";
} else if constexpr (std::is_same_v<T, dynamic::types::Date>) {
return "DATE";
} else if constexpr (std::is_same_v<T, dynamic::types::Timestamp>) {
return "TIMESTAMP";
} else if constexpr (std::is_same_v<T, dynamic::types::TimestampWithTZ>) {
return "TIMESTAMP WITH TIME ZONE";
} else if constexpr (std::is_same_v<T, dynamic::types::Unknown>) {
return "TEXT";
} else {
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
}
});
}
std::string update_to_sql(const dynamic::Update& _stmt) noexcept {
using namespace std::ranges::views;
const auto to_str = [](const auto& _set) -> std::string {
return wrap_in_quotes(_set.col.name) + " = " +
column_or_value_to_sql(_set.to);
};
std::stringstream stream;
stream << "UPDATE ";
stream << table_or_query_to_sql(_stmt.table);
stream << " SET ";
stream << internal::strings::join(
", ", internal::collect::vector(_stmt.sets | transform(to_str)));
if (_stmt.where) {
stream << " WHERE " << condition_to_sql(*_stmt.where);
}
stream << ";";
return stream.str();
}
std::string write_to_sql(const dynamic::Write& _stmt) noexcept {
using namespace std::ranges::views;
std::stringstream stream;
stream << "INSERT INTO ";
stream << table_or_query_to_sql(_stmt.table);
stream << " BY NAME ( SELECT ";
stream << internal::strings::join(
", ", internal::collect::vector(
_stmt.columns | transform([&](const auto _name) {
return wrap_in_quotes(_name) + " AS " + wrap_in_quotes(_name);
})));
stream << " FROM sqlgen_appended_data)";
stream << ";";
return stream.str();
}
} // namespace sqlgen::duckdb

5
src/sqlgen_duckdb.cpp Normal file
View File

@@ -0,0 +1,5 @@
#include "sqlgen/duckdb/Connection.cpp"
#include "sqlgen/duckdb/DuckDBConnection.cpp"
// #include "sqlgen/duckdb/Iterator.cpp"
// #include "sqlgen/duckdb/exec.cpp"
#include "sqlgen/duckdb/to_sql.cpp"

View File

@@ -8,6 +8,10 @@ if (SQLGEN_BUILD_DRY_TESTS_ONLY)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSQLGEN_BUILD_DRY_TESTS_ONLY")
endif()
if(SQLGEN_DUCKDB)
add_subdirectory(duckdb)
endif()
if(SQLGEN_MYSQL)
add_subdirectory(mysql)
endif()

View File

@@ -0,0 +1,19 @@
project(sqlgen-duckdb-tests)
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp")
add_executable(
sqlgen-duckdb-tests
${SOURCES}
)
target_precompile_headers(sqlgen-duckdb-tests PRIVATE [["sqlgen.hpp"]] <iostream> <string> <functional> <gtest/gtest.h>)
target_link_libraries(sqlgen-duckdb-tests PRIVATE sqlgen_tests_crt)
add_custom_command(TARGET sqlgen-duckdb-tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy -t $<TARGET_FILE_DIR:sqlgen-duckdb-tests> $<TARGET_RUNTIME_DLLS:sqlgen-duckdb-tests>
COMMAND_EXPAND_LISTS
)
find_package(GTest)
gtest_discover_tests(sqlgen-duckdb-tests)

View File

@@ -0,0 +1,64 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_aggregations {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_aggregations) {
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;
using namespace sqlgen::literals;
struct Children {
int num_children;
int num_last_names;
double avg_age;
double max_age;
double min_age;
double sum_age;
};
const auto get_children =
select_from<Person>(
avg("age"_c).as<"avg_age">(), count().as<"num_children">(),
max("age"_c).as<"max_age">(), min("age"_c).as<"min_age">(),
sum("age"_c).as<"sum_age">(),
count_distinct("last_name"_c).as<"num_last_names">()) |
where("age"_c < 18) | to<Children>;
const auto children = duckdb::connect()
.and_then(drop<Person> | if_exists)
.and_then(write(std::ref(people1)))
.and_then(get_children)
.value();
EXPECT_EQ(children.num_children, 3);
EXPECT_EQ(children.num_last_names, 1);
EXPECT_EQ(children.avg_age, 6.0);
EXPECT_EQ(children.max_age, 10.0);
EXPECT_EQ(children.min_age, 0.0);
EXPECT_EQ(children.sum_age, 18.0);
}
} // namespace test_aggregations

View File

@@ -0,0 +1,63 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_aggregations_with_nullable {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
std::optional<double> age;
};
TEST(duckdb, test_aggregations_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 = "Lisa", .last_name = "Simpson", .age = 8},
Person{.id = 3, .first_name = "Maggie", .last_name = "Simpson"}});
using namespace sqlgen;
using namespace sqlgen::literals;
struct Children {
int64_t num_children;
uint64_t num_last_names;
std::optional<double> avg_age;
std::optional<double> max_age;
std::optional<double> min_age;
std::optional<double> sum_age;
};
const auto get_children =
select_from<Person>(
avg("age"_c).as<"avg_age">(), count().as<"num_children">(),
max("age"_c).as<"max_age">(), min("age"_c).as<"min_age">(),
sum("age"_c).as<"sum_age">(),
count_distinct("last_name"_c).as<"num_last_names">()) |
where("age"_c < 18 or "age"_c.is_null()) | to<Children>;
const auto children = duckdb::connect()
.and_then(drop<Person> | if_exists)
.and_then(write(std::ref(people1)))
.and_then(get_children)
.value();
EXPECT_EQ(children.num_children, 3);
EXPECT_EQ(children.num_last_names, 1);
EXPECT_EQ(children.avg_age, 9.0);
EXPECT_EQ(children.max_age, 10.0);
EXPECT_EQ(children.min_age, 8.0);
EXPECT_EQ(children.sum_age, 18.0);
}
} // namespace test_aggregations_with_nullable

View File

@@ -0,0 +1,39 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_alpha_numeric {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
sqlgen::AlphaNumeric first_name;
sqlgen::AlphaNumeric last_name;
int age;
};
TEST(duckdb, test_alpha_numeric) {
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 conn = sqlgen::duckdb::connect();
sqlgen::write(conn, people1);
const auto people2 = sqlgen::read<std::vector<Person>>(conn).value();
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_alpha_numeric

View File

@@ -0,0 +1,47 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_alpha_numeric_query {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
sqlgen::AlphaNumeric first_name;
sqlgen::AlphaNumeric last_name;
int age;
};
sqlgen::Result<std::vector<Person>> get_people(
const auto& _conn, const sqlgen::AlphaNumeric& _first_name) {
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query =
sqlgen::read<std::vector<Person>> | where("first_name"_c == _first_name);
return query(_conn);
}
TEST(duckdb, test_alpha_numeric_query) {
const auto people1 = std::vector<Person>({Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}});
const auto conn = sqlgen::duckdb::connect();
sqlgen::write(conn, people1);
const auto people2 = sqlgen::AlphaNumeric::from_value("Homer")
.and_then([&](const auto& _first_name) {
return get_people(conn, _first_name);
})
.value();
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_alpha_numeric_query

View File

@@ -0,0 +1,45 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_auto_incr_primary_key {
struct Person {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_auto_incr_primary_key) {
auto people1 = std::vector<Person>(
{Person{.first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.first_name = "Bart", .last_name = "Simpson", .age = 10},
Person{.first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{.first_name = "Maggie", .last_name = "Simpson", .age = 0}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people2 = duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(sqlgen::read<std::vector<Person>> |
order_by("age"_c.desc()))
.value();
people1.at(0).id = 1;
people1.at(1).id = 2;
people1.at(2).id = 3;
people1.at(3).id = 4;
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_auto_incr_primary_key

View File

@@ -0,0 +1,52 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_boolean {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
bool has_children;
};
TEST(duckdb, test_boolean) {
const auto people1 = std::vector<Person>({Person{.id = 0,
.first_name = "Homer",
.last_name = "Simpson",
.has_children = true},
Person{.id = 1,
.first_name = "Bart",
.last_name = "Simpson",
.has_children = false},
Person{.id = 2,
.first_name = "Lisa",
.last_name = "Simpson",
.has_children = false},
Person{.id = 3,
.first_name = "Maggie",
.last_name = "Simpson",
.has_children = false}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto conn = duckdb::connect();
const auto people2 = sqlgen::write(conn, people1)
.and_then(sqlgen::read<std::vector<Person>>)
.value();
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_boolean

View File

@@ -0,0 +1,54 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_boolean_conditions {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
bool has_children;
};
TEST(duckdb, test_boolean_conditions) {
const auto people1 = std::vector<Person>({Person{.id = 0,
.first_name = "Homer",
.last_name = "Simpson",
.has_children = true},
Person{.id = 1,
.first_name = "Bart",
.last_name = "Simpson",
.has_children = false},
Person{.id = 2,
.first_name = "Lisa",
.last_name = "Simpson",
.has_children = false},
Person{.id = 3,
.first_name = "Maggie",
.last_name = "Simpson",
.has_children = false}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto homer =
duckdb::connect()
.and_then(sqlgen::write(people1))
.and_then(sqlgen::read<Person> | where("has_children"_c == true) |
order_by("id"_c))
.value();
const auto json1 = rfl::json::write(people1.at(0));
const auto json2 = rfl::json::write(homer);
EXPECT_EQ(json1, json2);
}
} // namespace test_boolean_conditions

View File

@@ -0,0 +1,59 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_boolean_update {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
bool has_children;
};
TEST(duckdb, test_boolean_update) {
auto people1 = std::vector<Person>({Person{.id = 0,
.first_name = "Homer",
.last_name = "Simpson",
.has_children = true},
Person{.id = 1,
.first_name = "Bart",
.last_name = "Simpson",
.has_children = false},
Person{.id = 2,
.first_name = "Lisa",
.last_name = "Simpson",
.has_children = false},
Person{.id = 3,
.first_name = "Maggie",
.last_name = "Simpson",
.has_children = false}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto conn = duckdb::connect();
const auto people2 =
sqlgen::write(conn, people1)
.and_then(update<Person>("has_children"_c.set(false)) |
where("has_children"_c == true))
.and_then(sqlgen::read<std::vector<Person>> |
where("has_children"_c == false) | order_by("id"_c))
.value();
people1.at(0).has_children = false;
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_boolean_update

View File

@@ -0,0 +1,45 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_cache {
struct User {
std::string name;
int age;
};
TEST(duckdb, test_cache) {
const auto conn = sqlgen::duckdb::connect();
const auto user = User{.name = "John", .age = 30};
sqlgen::write(conn, user);
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<User> | where("name"_c == "John");
const auto cached_query = sqlgen::cache<100>(query);
const auto user1 = conn.and_then(cache<100>(query)).value();
EXPECT_EQ(cached_query.cache(conn).size(), 1);
const auto user2 = cached_query(conn).value();
const auto user3 = cached_query(conn).value();
EXPECT_EQ(user1.name, "John");
EXPECT_EQ(user1.age, 30);
EXPECT_EQ(user2.name, "John");
EXPECT_EQ(user2.age, 30);
EXPECT_EQ(cached_query.cache(conn).size(), 1);
EXPECT_EQ(user3.name, "John");
EXPECT_EQ(user3.age, 30);
}
} // namespace test_cache

View File

@@ -0,0 +1,34 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_create_index {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_create_index) {
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people = sqlgen::duckdb::connect()
.and_then(create_table<Person> | if_not_exists)
.and_then(create_index<"person_ix", Person>(
"first_name"_c, "last_name"_c) |
if_not_exists)
.and_then(sqlgen::read<std::vector<Person>>);
const std::string expected = R"([])";
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_create_index

View File

@@ -0,0 +1,31 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_create_table {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_create_table) {
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people = sqlgen::duckdb::connect()
.and_then(create_table<Person> | if_not_exists)
.and_then(sqlgen::read<std::vector<Person>>);
const std::string expected = R"([])";
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_create_table

View File

@@ -0,0 +1,60 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_create_table_as {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
struct Name {
std::string first_name;
std::string last_name;
};
TEST(duckdb, test_create_table_as) {
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .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 names_query = select_from<Person>("first_name"_c, "last_name"_c);
const auto get_names = create_as<Name>(names_query) | if_not_exists;
const auto names = sqlgen::duckdb::connect()
.and_then(drop<Name> | if_exists)
.and_then(drop<Person> | if_exists)
.and_then(write(std::ref(people)))
.and_then(get_names)
.and_then(sqlgen::read<std::vector<Name>>)
.value();
const std::string expected_query =
R"(CREATE TABLE IF NOT EXISTS "Name" AS SELECT "first_name", "last_name" FROM "Person")";
const std::string expected =
R"([{"first_name":"Homer","last_name":"Simpson"},{"first_name":"Marge","last_name":"Simpson"},{"first_name":"Bart","last_name":"Simpson"},{"first_name":"Lisa","last_name":"Simpson"},{"first_name":"Maggie","last_name":"Simpson"}])";
EXPECT_EQ(duckdb::to_sql(get_names), expected_query);
EXPECT_EQ(rfl::json::write(names), expected);
}
} // namespace test_create_table_as

View File

@@ -0,0 +1,65 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_create_view_as {
struct Person {
static constexpr const char* tablename = "PEOPLE";
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
struct Name {
static constexpr const char* viewname = "NAMES";
static constexpr bool is_view = true;
std::string first_name;
std::string last_name;
};
TEST(duckdb, test_create_view_as) {
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .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 names_query = select_from<Person>("first_name"_c, "last_name"_c);
const auto get_names = create_as<Name>(names_query) | if_not_exists;
const auto names = duckdb::connect()
.and_then(drop<Name> | if_exists)
.and_then(drop<Person> | if_exists)
.and_then(write(std::ref(people)))
.and_then(get_names)
.and_then(sqlgen::read<std::vector<Name>>)
.value();
const std::string expected_query =
R"(CREATE VIEW IF NOT EXISTS "NAMES" AS SELECT "first_name", "last_name" FROM "PEOPLE")";
const std::string expected =
R"([{"first_name":"Homer","last_name":"Simpson"},{"first_name":"Marge","last_name":"Simpson"},{"first_name":"Bart","last_name":"Simpson"},{"first_name":"Lisa","last_name":"Simpson"},{"first_name":"Maggie","last_name":"Simpson"}])";
EXPECT_EQ(duckdb::to_sql(get_names), expected_query);
EXPECT_EQ(rfl::json::write(names), expected);
}
} // namespace test_create_view_as

View File

@@ -0,0 +1,48 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_delete_from {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_delete_from) {
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::duckdb::connect();
sqlgen::write(conn, people1);
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = delete_from<Person> | where("first_name"_c == "Hugo");
query(conn).value();
const auto people2 = sqlgen::read<std::vector<Person>>(conn).value();
const std::string expected =
R"([{"id":0,"first_name":"Homer","last_name":"Simpson","age":45},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0}])";
EXPECT_EQ(rfl::json::write(people2), expected);
}
} // namespace test_delete_from

View File

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

View File

@@ -0,0 +1,129 @@
#include <gtest/gtest.h>
#include <boost/lexical_cast.hpp>
#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <exception>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace sqlgen::parsing {
template <>
struct Parser<boost::uuids::uuid> {
using Type = boost::uuids::uuid;
static Result<boost::uuids::uuid> read(
const std::optional<std::string>& _str) noexcept {
if (!_str) {
return error("boost::uuids::uuid cannot be NULL.");
}
return boost::lexical_cast<boost::uuids::uuid>(*_str);
}
static std::optional<std::string> write(
const boost::uuids::uuid& _u) noexcept {
return boost::uuids::to_string(_u);
}
static dynamic::Type to_type() noexcept {
return sqlgen::dynamic::types::Dynamic{"TEXT"};
}
};
} // namespace sqlgen::parsing
namespace sqlgen::duckdb::parsing {
template <>
struct Parser<boost::uuids::uuid> {
using ResultingType = duckdb_string_t;
static Result<boost::uuids::uuid> read(const ResultingType* _r) noexcept {
return Parser<std::string>::read(_r).and_then(
[&](const std::string& _str) -> Result<boost::uuids::uuid> {
try {
return boost::lexical_cast<boost::uuids::uuid>(_str);
} catch (const std::exception& e) {
return error(e.what());
}
});
}
static Result<Nothing> write(const boost::uuids::uuid& _u,
duckdb_appender _appender) noexcept {
return Parser<std::string>::write(boost::uuids::to_string(_u), _appender);
}
};
} // namespace sqlgen::duckdb::parsing
namespace sqlgen::transpilation {
template <>
struct ToValue<boost::uuids::uuid> {
dynamic::Value operator()(const boost::uuids::uuid& _u) const {
return dynamic::Value{dynamic::String{.val = boost::uuids::to_string(_u)}};
}
};
} // namespace sqlgen::transpilation
/// For the JSON serialization - not needed for
/// the actual DB operations.
namespace rfl {
template <>
struct Reflector<boost::uuids::uuid> {
using ReflType = std::string;
static boost::uuids::uuid to(const std::string& _str) {
return boost::lexical_cast<boost::uuids::uuid>(_str);
}
static std::string from(const boost::uuids::uuid& _u) {
return boost::uuids::to_string(_u);
}
};
} // namespace rfl
namespace test_dynamic_type {
struct Person {
sqlgen::PrimaryKey<boost::uuids::uuid> id =
boost::uuids::uuid(boost::uuids::random_generator()());
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_dynamic_type) {
const auto people1 = std::vector<Person>(
{Person{.first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.first_name = "Bart", .last_name = "Simpson", .age = 10},
Person{.first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{.first_name = "Maggie", .last_name = "Simpson", .age = 0}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto conn = duckdb::connect().and_then(drop<Person> | if_exists);
const auto people2 = sqlgen::write(conn, people1)
.and_then(sqlgen::read<std::vector<Person>> |
where("id"_c == people1.front().id()))
.value();
const auto json1 = rfl::json::write(std::vector<Person>({people1.front()}));
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_dynamic_type

View File

@@ -0,0 +1,100 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_enum_cross_table {
enum class AccessRestriction { PUBLIC, INTERNAL, CONFIDENTIAL };
struct Employee {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
std::string first_name;
std::string last_name;
AccessRestriction access_level;
};
struct Document {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
AccessRestriction min_access_level;
std::string name;
std::string path;
};
TEST(duckdb, test_enum_cross_table) {
using namespace sqlgen;
using namespace sqlgen::literals;
auto employees = std::vector<Employee>({
Employee{.first_name = "Homer",
.last_name = "Simpson",
.access_level = AccessRestriction::PUBLIC},
Employee{.first_name = "Waylon",
.last_name = "Smithers",
.access_level = AccessRestriction::INTERNAL},
Employee{.first_name = "Montgomery",
.last_name = "Burns",
.access_level = AccessRestriction::CONFIDENTIAL},
});
auto documents = std::vector<Document>({
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Staff Memo",
.path = "/documents/powerplant/staff_memo.txt"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Operations Report",
.path = "/documents/powerplant/operations_report.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Budget Q1",
.path = "/documents/powerplant/budget_q1.pdf"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "HR Policies",
.path = "/documents/powerplant/hr_policies.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Executive Summary",
.path = "/documents/powerplant/executive_summary.docx"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Release Notes",
.path = "/documents/powerplant/release_notes.txt"},
});
const auto conn = duckdb::connect()
.and_then(drop<Employee> | if_exists)
.and_then(drop<Document> | if_exists)
.and_then(write(employees))
.and_then(write(documents));
const auto smithers = conn.and_then(sqlgen::read<Employee> |
where("last_name"_c == "Smithers" and
"first_name"_c == "Waylon"))
.value();
const auto smithers_level = smithers.access_level;
const auto smithers_documents =
conn.and_then(sqlgen::read<std::vector<Document>> |
where("min_access_level"_c == smithers_level ||
"min_access_level"_c == AccessRestriction::PUBLIC) |
order_by("name"_c))
.value();
const auto expected_ids = std::set<uint32_t>{1, 2, 4, 5, 7, 9};
std::set<uint32_t> actual_ids;
for (const auto &d : smithers_documents) {
actual_ids.emplace(d.id());
}
EXPECT_EQ(expected_ids, actual_ids);
}
} // namespace test_enum_cross_table

View File

@@ -0,0 +1,84 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_enum_lookup {
enum class AccessRestriction { PUBLIC, INTERNAL, CONFIDENTIAL };
struct Document {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
AccessRestriction min_access_level;
std::string name;
std::string path;
};
TEST(duckdb, test_enum_lookup) {
auto documents = std::vector<Document>({
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Staff Memo",
.path = "/documents/powerplant/staff_memo.txt"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Operations Report",
.path = "/documents/powerplant/operations_report.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Budget Q1",
.path = "/documents/powerplant/budget_q1.pdf"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "HR Policies",
.path = "/documents/powerplant/hr_policies.pdf"},
Document{.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.min_access_level = AccessRestriction::CONFIDENTIAL,
.name = "Executive Summary",
.path = "/documents/powerplant/executive_summary.docx"},
Document{.min_access_level = AccessRestriction::INTERNAL,
.name = "Release Notes",
.path = "/documents/powerplant/release_notes.txt"},
});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto public_documents =
duckdb::connect()
.and_then(drop<Document> | if_exists)
.and_then(write(std::ref(documents)))
.and_then(sqlgen::read<std::vector<Document>> |
where("min_access_level"_c == AccessRestriction::PUBLIC) |
order_by("name"_c.desc()))
.value();
const auto expected = std::vector<Document>({
Document{.id = 7,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Team Photo",
.path = "/documents/powerplant/team.jpg"},
Document{.id = 4,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Project Plan",
.path = "/documents/powerplant/project_plan.md"},
Document{.id = 1,
.min_access_level = AccessRestriction::PUBLIC,
.name = "Power Plant Safety Manual",
.path = "/documents/powerplant/safety_manual.txt"},
});
const auto json1 = rfl::json::write(expected);
const auto json2 = rfl::json::write(public_documents);
EXPECT_EQ(json1, json2);
}
} // namespace test_enum_lookup

View File

@@ -0,0 +1,59 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_enum_namespace {
namespace first {
enum class IdenticallyNamed { VALUE0, VALUE1, VALUE2 };
}
namespace second {
enum class IdenticallyNamed { VALUE3, VALUE4, VALUE5 };
}
struct MultiStruct {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
first::IdenticallyNamed enum_one;
second::IdenticallyNamed enum_two;
};
TEST(duckdb, test_enum_namespace) {
using namespace sqlgen;
using namespace sqlgen::literals;
auto objects = std::vector<MultiStruct>({
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE0,
.enum_two = second::IdenticallyNamed::VALUE3},
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE1,
.enum_two = second::IdenticallyNamed::VALUE4},
MultiStruct{.enum_one = first::IdenticallyNamed::VALUE2,
.enum_two = second::IdenticallyNamed::VALUE5},
});
const auto conn = duckdb::connect();
conn.and_then(drop<MultiStruct> | if_exists);
write(conn, objects);
const auto read_objects =
sqlgen::read<std::vector<MultiStruct>>(conn).value();
std::vector<uint32_t> actual_ids;
for (const auto& obj : read_objects) {
if (obj.enum_one == first::IdenticallyNamed::VALUE0) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE3);
} else if (obj.enum_one == first::IdenticallyNamed::VALUE1) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE4);
} else if (obj.enum_one == first::IdenticallyNamed::VALUE2) {
EXPECT_EQ(obj.enum_two, second::IdenticallyNamed::VALUE5);
} else {
FAIL() << "Unexpected enum value";
}
}
}
} // namespace test_enum_namespace

View File

@@ -0,0 +1,45 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_write_and_read {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
struct Employee {
static constexpr const char* tablename = "EMPLOYEES";
sqlgen::Flatten<Person> person;
float salary;
};
TEST(duckdb, test_flatten) {
const auto people1 =
std::vector<Employee>({Employee{.person = Person{.id = 0,
.first_name = "Homer",
.last_name = "Simpson",
.age = 45},
.salary = 60000.0}});
const auto conn = sqlgen::duckdb::connect();
sqlgen::write(conn, people1);
const auto people2 = sqlgen::read<std::vector<Employee>>(conn).value();
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_write_and_read

View File

@@ -0,0 +1,58 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_foreign_key {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
struct Relationship {
sqlgen::ForeignKey<uint32_t, Person, "id"> parent_id;
uint32_t child_id;
};
TEST(duckdb, test_foreign_key) {
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 relationships =
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
Relationship{.parent_id = 0, .child_id = 3},
Relationship{.parent_id = 0, .child_id = 4},
Relationship{.parent_id = 1, .child_id = 2},
Relationship{.parent_id = 1, .child_id = 3},
Relationship{.parent_id = 1, .child_id = 4}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people = duckdb::connect()
.and_then(drop<Person> | if_exists)
.and_then(drop<Relationship> | if_exists)
.and_then(begin_transaction)
.and_then(create_table<Person>)
.and_then(create_table<Relationship>)
.and_then(insert(std::ref(people1)))
.and_then(insert(std::ref(relationships)))
.and_then(drop<Relationship> | if_exists)
.and_then(drop<Person> | if_exists)
.and_then(commit)
.value();
}
} // namespace test_foreign_key

View File

@@ -0,0 +1,69 @@
#include <gtest/gtest.h>
#include <optional>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_full_join {
TEST(duckdb, test_full_join) {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
};
struct Pet {
sqlgen::PrimaryKey<uint32_t> id;
std::string name;
uint32_t owner_id;
};
const auto people = std::vector<Person>({
Person{.id = 1, .first_name = "Homer", .last_name = "Simpson"},
Person{.id = 2, .first_name = "Marge", .last_name = "Simpson"},
Person{.id = 3, .first_name = "Bart", .last_name = "Simpson"},
Person{.id = 4, .first_name = "Lisa", .last_name = "Simpson"},
});
const auto pets = std::vector<Pet>({
Pet{.id = 1, .name = "Santa's Little Helper", .owner_id = 1},
Pet{.id = 2, .name = "Snowball", .owner_id = 4},
Pet{.id = 3, .name = "Mr. Teeny", .owner_id = 99},
});
using namespace sqlgen;
using namespace sqlgen::literals;
struct PersonAndPet {
std::optional<std::string> first_name;
std::optional<std::string> last_name;
std::optional<std::string> pet_name;
};
const auto get_all =
select_from<Person, "t1">("first_name"_t1 | as<"first_name">,
"last_name"_t1 | as<"last_name">,
"name"_t2 | as<"pet_name">) |
full_join<Pet, "t2">("id"_t1 == "owner_id"_t2) |
order_by("id"_t1, "id"_t2) | to<std::vector<PersonAndPet>>;
const auto result = duckdb::connect()
.and_then(write(std::ref(people)))
.and_then(write(std::ref(pets)))
.and_then(get_all)
.value();
const std::string expected_query =
R"(SELECT t1."first_name" AS "first_name", t1."last_name" AS "last_name", t2."name" AS "pet_name" FROM "Person" t1 FULL JOIN "Pet" t2 ON t1."id" = t2."owner_id" ORDER BY t1."id", t2."id")";
const std::string expected_json =
R"([{"first_name":"Homer","last_name":"Simpson","pet_name":"Santa's Little Helper"},{"first_name":"Marge","last_name":"Simpson"},{"first_name":"Bart","last_name":"Simpson"},{"first_name":"Lisa","last_name":"Simpson","pet_name":"Snowball"},{"pet_name":"Mr. Teeny"}])";
EXPECT_EQ(duckdb::to_sql(get_all), expected_query);
EXPECT_EQ(rfl::json::write(result), expected_json);
}
} // namespace test_full_join

View File

@@ -0,0 +1,67 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_group_by {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_group_by) {
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;
using namespace sqlgen::literals;
struct Children {
std::string last_name;
int num_children;
int num_last_names;
double avg_age;
double max_age;
double min_age;
double sum_age;
};
const auto get_children =
select_from<Person>(
"last_name"_c, avg("age"_c).as<"avg_age">(),
count().as<"num_children">(), max("age"_c).as<"max_age">(),
min("age"_c).as<"min_age">(), sum("age"_c).as<"sum_age">(),
count_distinct("last_name"_c).as<"num_last_names">()) |
where("age"_c < 18) | group_by("last_name"_c) | to<std::vector<Children>>;
const auto children = duckdb::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).num_children, 3);
EXPECT_EQ(children.at(0).num_last_names, 1);
EXPECT_EQ(children.at(0).avg_age, 6.0);
EXPECT_EQ(children.at(0).max_age, 10.0);
EXPECT_EQ(children.at(0).min_age, 0.0);
EXPECT_EQ(children.at(0).sum_age, 18.0);
}
} // namespace test_group_by

View File

@@ -0,0 +1,63 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.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(duckdb, 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;
using namespace sqlgen::literals;
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 conn = duckdb::connect()
.and_then(drop<Person> | if_exists)
.and_then(write(std::ref(people1)));
const auto children = get_children(conn).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,32 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_hello_world {
struct User {
std::string name;
int age;
};
TEST(duckdb, test_hello_world) {
// Connect to duckdb database
const auto conn = sqlgen::duckdb::connect("test.db");
// Create and insert a user
const auto user = User{.name = "John", .age = 30};
sqlgen::write(conn, user);
// Read all users
const auto users = sqlgen::read<std::vector<User>>(conn).value();
EXPECT_EQ(users.size(), 1);
EXPECT_EQ(users.at(0).name, "John");
EXPECT_EQ(users.at(0).age, 30);
}
} // namespace test_hello_world

48
tests/duckdb/test_in.cpp Normal file
View File

@@ -0,0 +1,48 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_in {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_in) {
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;
using namespace sqlgen::literals;
const auto people2 =
duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(sqlgen::read<std::vector<Person>> |
where("first_name"_c.in("Bart", "Lisa", "Maggie")) |
order_by("age"_c))
.value();
const std::string expected1 =
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);
}
} // namespace test_in

View File

@@ -0,0 +1,49 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_in_vec {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_in_vec) {
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;
using namespace sqlgen::literals;
const auto people2 =
duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(sqlgen::read<std::vector<Person>> |
where("first_name"_c.in(
std::vector<std::string>({"Bart", "Lisa", "Maggie"}))) |
order_by("age"_c))
.value();
const std::string expected1 =
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);
}
} // namespace test_in_vec

View File

@@ -0,0 +1,44 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_insert_and_read {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_insert_and_read) {
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;
using namespace sqlgen::literals;
const auto people2 = duckdb::connect()
.and_then(begin_transaction)
.and_then(create_table<Person> | if_not_exists)
.and_then(insert(people1))
.and_then(commit)
.and_then(sqlgen::read<std::vector<Person>>)
.value();
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_insert_and_read

View File

@@ -0,0 +1,44 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_insert_by_ref_and_read {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_insert_by_ref_and_read) {
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;
using namespace sqlgen::literals;
const auto people2 = duckdb::connect()
.and_then(begin_transaction)
.and_then(create_table<Person> | if_not_exists)
.and_then(insert(std::ref(people1)))
.and_then(commit)
.and_then(sqlgen::read<std::vector<Person>>)
.value();
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_insert_by_ref_and_read

View File

@@ -0,0 +1,57 @@
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_insert_fail {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_insert_fail) {
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;
using namespace sqlgen::literals;
const auto conn = duckdb::connect()
.and_then(drop<Person> | if_exists)
.and_then(write(people1.at(0)));
const auto res = conn.and_then(insert(people1.at(0)));
// Should fail - duplicate key violation.
EXPECT_FALSE(res && true);
const auto people2 =
conn.and_then(begin_transaction)
.and_then(insert(people1 | std::ranges::views::drop(1)))
.and_then(commit)
.and_then(sqlgen::read<std::vector<Person>>)
.value();
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_insert_fail
#endif

View File

@@ -0,0 +1,70 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_insert_or_replace {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_insert_or_replace) {
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 people2 = std::vector<Person>({Person{.id = 1,
.first_name = "Bartholomew",
.last_name = "Simpson",
.age = 10},
Person{.id = 3,
.first_name = "Margaret",
.last_name = "Simpson",
.age = 1}});
const auto people3 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1,
.first_name = "Bartholomew",
.last_name = "Simpson",
.age = 10},
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{.id = 3,
.first_name = "Margaret",
.last_name = "Simpson",
.age = 1}});
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people4 =
duckdb::connect()
.and_then(begin_transaction)
.and_then(create_table<Person> | if_not_exists)
.and_then(insert(std::ref(people1)))
.and_then(commit)
.and_then(begin_transaction)
.and_then(insert_or_replace(std::ref(people2)))
.and_then(commit)
.and_then(sqlgen::read<std::vector<Person>> | order_by("id"_c))
.value();
const auto json3 = rfl::json::write(people3);
const auto json4 = rfl::json::write(people4);
EXPECT_EQ(json3, json4);
}
} // namespace test_insert_or_replace

View File

@@ -0,0 +1,55 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.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(duckdb, 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;
using namespace sqlgen::literals;
const auto conn = duckdb::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,51 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_join {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
double age;
};
TEST(duckdb, test_join) {
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;
using namespace sqlgen::literals;
const auto get_people =
select_from<Person, "t1">(
"id"_t1 | as<"id">, "first_name"_t1 | as<"first_name">,
"last_name"_t2 | as<"last_name">, "age"_t2 | as<"age">) |
inner_join<Person, "t2">("id"_t1 == "id"_t2) | order_by("id"_t1) |
to<std::vector<Person>>;
const auto people = duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(get_people)
.value();
const std::string expected_query =
R"(SELECT t1."id" AS "id", t1."first_name" AS "first_name", t2."last_name" AS "last_name", t2."age" AS "age" FROM "Person" t1 INNER JOIN "Person" t2 ON t1."id" = t2."id" ORDER BY t1."id")";
const std::string expected =
R"([{"id":0,"first_name":"Homer","last_name":"Simpson","age":45.0},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10.0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8.0},{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0.0}])";
EXPECT_EQ(duckdb::to_sql(get_people), expected_query);
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_join

View File

@@ -0,0 +1,86 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_joins_from {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
double age;
};
struct Relationship {
uint32_t parent_id;
uint32_t child_id;
};
TEST(duckdb, test_joins_from) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .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 relationships =
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
Relationship{.parent_id = 0, .child_id = 3},
Relationship{.parent_id = 0, .child_id = 4},
Relationship{.parent_id = 1, .child_id = 2},
Relationship{.parent_id = 1, .child_id = 3},
Relationship{.parent_id = 1, .child_id = 4}});
using namespace sqlgen;
using namespace sqlgen::literals;
struct ParentAndChild {
std::string last_name;
std::string first_name_parent;
std::string first_name_child;
double parent_age_at_birth;
};
const auto get_parents =
select_from<Person, "t1">(
"child_id"_t2 | as<"id">, "first_name"_t1 | as<"first_name">,
"last_name"_t1 | as<"last_name">, "age"_t1 | as<"age">) |
inner_join<Relationship, "t2">("id"_t1 == "parent_id"_t2);
const auto get_people =
select_from<"t1">(get_parents, "last_name"_t1 | as<"last_name">,
"first_name"_t1 | as<"first_name_parent">,
"first_name"_t2 | as<"first_name_child">,
("age"_t1 - "age"_t2) | as<"parent_age_at_birth">) |
inner_join<Person, "t2">("id"_t1 == "id"_t2) |
order_by("id"_t2, "id"_t1) | to<std::vector<ParentAndChild>>;
const auto people = duckdb::connect()
.and_then(drop<Person> | if_exists)
.and_then(drop<Relationship> | if_exists)
.and_then(write(std::ref(people1)))
.and_then(write(std::ref(relationships)))
.and_then(get_people)
.value();
const std::string expected_query =
R"(SELECT t1."last_name" AS "last_name", t1."first_name" AS "first_name_parent", t2."first_name" AS "first_name_child", (t1."age") - (t2."age") AS "parent_age_at_birth" FROM (SELECT t2."child_id" AS "id", t1."first_name" AS "first_name", t1."last_name" AS "last_name", t1."age" AS "age" FROM "Person" t1 INNER JOIN "Relationship" t2 ON t1."id" = t2."parent_id") t1 INNER JOIN "Person" t2 ON t1."id" = t2."id" ORDER BY t2."id", t1."id")";
const std::string expected =
R"([{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Bart","parent_age_at_birth":35.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Bart","parent_age_at_birth":30.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Lisa","parent_age_at_birth":37.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Lisa","parent_age_at_birth":32.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Maggie","parent_age_at_birth":45.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Maggie","parent_age_at_birth":40.0}])";
EXPECT_EQ(duckdb::to_sql(get_people), expected_query);
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_joins_from

View File

@@ -0,0 +1,82 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_joins_nested {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
double age;
};
struct Relationship {
uint32_t parent_id;
uint32_t child_id;
};
TEST(duckdb, test_joins_nested) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .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 relationships =
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
Relationship{.parent_id = 0, .child_id = 3},
Relationship{.parent_id = 0, .child_id = 4},
Relationship{.parent_id = 1, .child_id = 2},
Relationship{.parent_id = 1, .child_id = 3},
Relationship{.parent_id = 1, .child_id = 4}});
using namespace sqlgen;
using namespace sqlgen::literals;
struct ParentAndChild {
std::string last_name;
std::string first_name_parent;
std::string first_name_child;
double parent_age_at_birth;
};
const auto get_children =
select_from<Relationship, "t1">("parent_id"_t1 | as<"id">,
"first_name"_t2 | as<"first_name">,
"age"_t2 | as<"age">) |
inner_join<Person, "t2">("id"_t2 == "child_id"_t1);
const auto get_people =
select_from<Person, "t1">(
"last_name"_t1 | as<"last_name">,
"first_name"_t1 | as<"first_name_parent">,
"first_name"_t2 | as<"first_name_child">,
("age"_t1 - "age"_t2) | as<"parent_age_at_birth">) |
inner_join<"t2">(get_children, "id"_t1 == "id"_t2) |
order_by("id"_t1, "id"_t2) | to<std::vector<ParentAndChild>>;
const auto people = duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(write(std::ref(relationships)))
.and_then(get_people)
.value();
const std::string expected_query =
R"(SELECT t1."last_name" AS "last_name", t1."first_name" AS "first_name_parent", t2."first_name" AS "first_name_child", (t1."age") - (t2."age") AS "parent_age_at_birth" FROM "Person" t1 INNER JOIN (SELECT t1."parent_id" AS "id", t2."first_name" AS "first_name", t2."age" AS "age" FROM "Relationship" t1 INNER JOIN "Person" t2 ON t2."id" = t1."child_id") t2 ON t1."id" = t2."id" ORDER BY t1."id", t2."id")";
const std::string expected =
R"([{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Maggie","parent_age_at_birth":45.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Lisa","parent_age_at_birth":37.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Bart","parent_age_at_birth":35.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Maggie","parent_age_at_birth":40.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Lisa","parent_age_at_birth":32.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Bart","parent_age_at_birth":30.0}])";
EXPECT_EQ(duckdb::to_sql(get_people), expected_query);
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_joins_nested

View File

@@ -0,0 +1,81 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_joins_nested_grouped {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
double age;
};
struct Relationship {
uint32_t parent_id;
uint32_t child_id;
};
TEST(duckdb, test_joins_nested_grouped) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .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 relationships =
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
Relationship{.parent_id = 0, .child_id = 3},
Relationship{.parent_id = 0, .child_id = 4},
Relationship{.parent_id = 1, .child_id = 2},
Relationship{.parent_id = 1, .child_id = 3},
Relationship{.parent_id = 1, .child_id = 4}});
using namespace sqlgen;
using namespace sqlgen::literals;
struct ParentAndChild {
std::string last_name;
std::string first_name_child;
double avg_parent_age_at_birth;
};
const auto get_children =
select_from<Relationship, "t1">("parent_id"_t1 | as<"id">,
"first_name"_t2 | as<"first_name">,
"age"_t2 | as<"age">) |
inner_join<Person, "t2">("id"_t2 == "child_id"_t1);
const auto get_people =
select_from<Person, "t1">(
"last_name"_t1 | as<"last_name">,
"first_name"_t2 | as<"first_name_child">,
avg("age"_t1 - "age"_t2) | as<"avg_parent_age_at_birth">) |
inner_join<"t2">(get_children, "id"_t1 == "id"_t2) |
group_by("last_name"_t1, "first_name"_t2) | order_by("first_name"_t2) |
to<std::vector<ParentAndChild>>;
const auto people = duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(write(std::ref(relationships)))
.and_then(get_people)
.value();
const std::string expected_query =
R"(SELECT t1."last_name" AS "last_name", t2."first_name" AS "first_name_child", AVG((t1."age") - (t2."age")) AS "avg_parent_age_at_birth" FROM "Person" t1 INNER JOIN (SELECT t1."parent_id" AS "id", t2."first_name" AS "first_name", t2."age" AS "age" FROM "Relationship" t1 INNER JOIN "Person" t2 ON t2."id" = t1."child_id") t2 ON t1."id" = t2."id" GROUP BY t1."last_name", t2."first_name" ORDER BY t2."first_name")";
const std::string expected =
R"([{"last_name":"Simpson","first_name_child":"Bart","avg_parent_age_at_birth":32.5},{"last_name":"Simpson","first_name_child":"Lisa","avg_parent_age_at_birth":34.5},{"last_name":"Simpson","first_name_child":"Maggie","avg_parent_age_at_birth":42.5}])";
EXPECT_EQ(duckdb::to_sql(get_people), expected_query);
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_joins_nested_grouped

View File

@@ -0,0 +1,77 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_joins_two_tables {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
double age;
};
struct Relationship {
uint32_t parent_id;
uint32_t child_id;
};
TEST(duckdb, test_joins_two_tables) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .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 relationships =
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
Relationship{.parent_id = 0, .child_id = 3},
Relationship{.parent_id = 0, .child_id = 4},
Relationship{.parent_id = 1, .child_id = 2},
Relationship{.parent_id = 1, .child_id = 3},
Relationship{.parent_id = 1, .child_id = 4}});
using namespace sqlgen;
using namespace sqlgen::literals;
struct ParentAndChild {
std::string last_name;
std::string first_name_parent;
std::string first_name_child;
double parent_age_at_birth;
};
const auto get_people =
select_from<Person, "t1">(
"last_name"_t1 | as<"last_name">,
"first_name"_t1 | as<"first_name_parent">,
"first_name"_t3 | as<"first_name_child">,
("age"_t1 - "age"_t3) | as<"parent_age_at_birth">) |
inner_join<Relationship, "t2">("id"_t1 == "parent_id"_t2) |
inner_join<Person, "t3">("id"_t3 == "child_id"_t2) |
order_by("id"_t1, "id"_t3) | to<std::vector<ParentAndChild>>;
const auto people = duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(write(std::ref(relationships)))
.and_then(get_people)
.value();
const std::string expected_query =
R"(SELECT t1."last_name" AS "last_name", t1."first_name" AS "first_name_parent", t3."first_name" AS "first_name_child", (t1."age") - (t3."age") AS "parent_age_at_birth" FROM "Person" t1 INNER JOIN "Relationship" t2 ON t1."id" = t2."parent_id" INNER JOIN "Person" t3 ON t3."id" = t2."child_id" ORDER BY t1."id", t3."id")";
const std::string expected =
R"([{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Bart","parent_age_at_birth":35.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Lisa","parent_age_at_birth":37.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Maggie","parent_age_at_birth":45.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Bart","parent_age_at_birth":30.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Lisa","parent_age_at_birth":32.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Maggie","parent_age_at_birth":40.0}])";
EXPECT_EQ(duckdb::to_sql(get_people), expected_query);
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_joins_two_tables

View File

@@ -0,0 +1,78 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_joins_two_tables_grouped {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
double age;
};
struct Relationship {
uint32_t parent_id;
uint32_t child_id;
};
TEST(duckdb, test_joins_two_tables_grouped) {
const auto people1 = std::vector<Person>(
{Person{
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .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 relationships =
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
Relationship{.parent_id = 0, .child_id = 3},
Relationship{.parent_id = 0, .child_id = 4},
Relationship{.parent_id = 1, .child_id = 2},
Relationship{.parent_id = 1, .child_id = 3},
Relationship{.parent_id = 1, .child_id = 4}});
using namespace sqlgen;
using namespace sqlgen::literals;
struct ParentAndChild {
std::string last_name;
std::string first_name_child;
double avg_parent_age_at_birth;
};
const auto get_people =
select_from<Person, "t1">(
"last_name"_t1 | as<"last_name">,
"first_name"_t3 | as<"first_name_child">,
avg("age"_t1 - "age"_t3) | as<"avg_parent_age_at_birth">) |
inner_join<Relationship, "t2">("id"_t1 == "parent_id"_t2) |
inner_join<Person, "t3">("id"_t3 == "child_id"_t2) |
group_by("last_name"_t1, "first_name"_t3) |
order_by("last_name"_t1, "first_name"_t3) |
to<std::vector<ParentAndChild>>;
const auto people = duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(write(std::ref(relationships)))
.and_then(get_people)
.value();
const std::string expected_query =
R"(SELECT t1."last_name" AS "last_name", t3."first_name" AS "first_name_child", AVG((t1."age") - (t3."age")) AS "avg_parent_age_at_birth" FROM "Person" t1 INNER JOIN "Relationship" t2 ON t1."id" = t2."parent_id" INNER JOIN "Person" t3 ON t3."id" = t2."child_id" GROUP BY t1."last_name", t3."first_name" ORDER BY t1."last_name", t3."first_name")";
const std::string expected =
R"([{"last_name":"Simpson","first_name_child":"Bart","avg_parent_age_at_birth":32.5},{"last_name":"Simpson","first_name_child":"Lisa","avg_parent_age_at_birth":34.5},{"last_name":"Simpson","first_name_child":"Maggie","avg_parent_age_at_birth":42.5}])";
EXPECT_EQ(duckdb::to_sql(get_people), expected_query);
EXPECT_EQ(rfl::json::write(people), expected);
}
} // namespace test_joins_two_tables_grouped

View File

@@ -0,0 +1,45 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_json {
struct Person {
std::string first_name;
std::string last_name;
int age;
sqlgen::JSON<std::optional<std::vector<Person>>> children;
};
TEST(duckdb, test_json) {
const auto children = std::vector<Person>(
{Person{.first_name = "Bart", .last_name = "Simpson", .age = 10},
Person{.first_name = "Lisa", .last_name = "Simpson", .age = 8},
Person{.first_name = "Maggie", .last_name = "Simpson", .age = 0}});
const auto homer = Person{.first_name = "Homer",
.last_name = "Simpson",
.age = 45,
.children = children};
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people2 = sqlgen::duckdb::connect()
.and_then(drop<Person> | if_exists)
.and_then(write(std::ref(homer)))
.and_then(sqlgen::read<std::vector<Person>>)
.value();
const std::string expected =
R"([{"first_name":"Homer","last_name":"Simpson","age":45,"children":[{"first_name":"Bart","last_name":"Simpson","age":10},{"first_name":"Lisa","last_name":"Simpson","age":8},{"first_name":"Maggie","last_name":"Simpson","age":0}]}])";
EXPECT_EQ(rfl::json::write(people2), expected);
}
} // namespace test_json

View File

@@ -0,0 +1,69 @@
#include <gtest/gtest.h>
#include <optional>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_left_join {
TEST(duckdb, test_left_join) {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
};
struct Pet {
sqlgen::PrimaryKey<uint32_t> id;
std::string name;
uint32_t owner_id;
};
const auto people = std::vector<Person>({
Person{.id = 1, .first_name = "Homer", .last_name = "Simpson"},
Person{.id = 2, .first_name = "Marge", .last_name = "Simpson"},
Person{.id = 3, .first_name = "Bart", .last_name = "Simpson"},
Person{.id = 4, .first_name = "Lisa", .last_name = "Simpson"},
});
const auto pets = std::vector<Pet>({
Pet{.id = 1, .name = "Santa's Little Helper", .owner_id = 1},
Pet{.id = 2, .name = "Snowball", .owner_id = 4},
Pet{.id = 3, .name = "Mr. Teeny", .owner_id = 99},
});
using namespace sqlgen;
using namespace sqlgen::literals;
struct PersonWithPet {
std::string first_name;
std::string last_name;
std::optional<std::string> pet_name;
};
const auto get_people_with_pets =
select_from<Person, "t1">("first_name"_t1 | as<"first_name">,
"last_name"_t1 | as<"last_name">,
"name"_t2 | as<"pet_name">) |
left_join<Pet, "t2">("id"_t1 == "owner_id"_t2) | order_by("id"_t1) |
to<std::vector<PersonWithPet>>;
const auto result = duckdb::connect()
.and_then(write(std::ref(people)))
.and_then(write(std::ref(pets)))
.and_then(get_people_with_pets)
.value();
const std::string expected_query =
R"(SELECT t1."first_name" AS "first_name", t1."last_name" AS "last_name", t2."name" AS "pet_name" FROM "Person" t1 LEFT JOIN "Pet" t2 ON t1."id" = t2."owner_id" ORDER BY t1."id")";
const std::string expected_json =
R"([{"first_name":"Homer","last_name":"Simpson","pet_name":"Santa's Little Helper"},{"first_name":"Marge","last_name":"Simpson"},{"first_name":"Bart","last_name":"Simpson"},{"first_name":"Lisa","last_name":"Simpson","pet_name":"Snowball"}])";
EXPECT_EQ(duckdb::to_sql(get_people_with_pets), expected_query);
EXPECT_EQ(rfl::json::write(result), expected_json);
}
} // namespace test_left_join

View File

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

View File

@@ -0,0 +1,47 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_limit {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_limit) {
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::duckdb::connect();
sqlgen::write(conn, people1);
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query =
sqlgen::read<std::vector<Person>> | order_by("age"_c) | limit(2);
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}])";
EXPECT_EQ(rfl::json::write(people2), expected);
}
} // namespace test_limit

View File

@@ -0,0 +1,48 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_not_in {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_not_in) {
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;
using namespace sqlgen::literals;
const auto people2 =
duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(sqlgen::read<std::vector<Person>> |
where("first_name"_c.not_in("Homer", "Hugo")) |
order_by("age"_c))
.value();
const std::string expected1 =
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);
}
} // namespace test_not_in

View File

@@ -0,0 +1,49 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_not_in_vec {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_not_in_vec) {
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;
using namespace sqlgen::literals;
const auto people2 =
duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(sqlgen::read<std::vector<Person>> |
where("first_name"_c.not_in(
std::vector<std::string>({"Homer", "Hugo"}))) |
order_by("age"_c))
.value();
const std::string expected1 =
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);
}
} // namespace test_not_in_vec

View File

@@ -0,0 +1,76 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_operations {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, 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;
using namespace sqlgen::literals;
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 = duckdb::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,63 @@
#include <gtest/gtest.h>
#include <optional>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.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(duckdb, 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;
using namespace sqlgen::literals;
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 = duckdb::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

@@ -0,0 +1,47 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_order_by {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_order_by) {
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::duckdb::connect();
sqlgen::write(conn, people1);
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>> |
order_by("age"_c, "first_name"_c.desc());
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":4,"first_name":"Hugo","last_name":"Simpson","age":10},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10},{"id":0,"first_name":"Homer","last_name":"Simpson","age":45}])";
EXPECT_EQ(rfl::json::write(people2), expected);
}
} // namespace test_order_by

View File

@@ -0,0 +1,47 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_range {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_range) {
using namespace std::ranges::views;
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 conn = sqlgen::duckdb::connect();
sqlgen::write(conn, people1);
const auto people2 = sqlgen::read<sqlgen::Range<Person>>(conn).value();
const auto first_names =
sqlgen::internal::collect::vector(people2 | transform([](const auto& _r) {
return _r.value().first_name;
}));
EXPECT_EQ(first_names.at(0), "Homer");
EXPECT_EQ(first_names.at(1), "Bart");
EXPECT_EQ(first_names.at(2), "Lisa");
EXPECT_EQ(first_names.at(3), "Maggie");
}
} // namespace test_range

View File

@@ -0,0 +1,52 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_range_select_from {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_range_select_from) {
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;
using namespace sqlgen::literals;
const auto people2 =
sqlgen::duckdb::connect()
.and_then(sqlgen::write(std::ref(people1)))
.and_then(select_from<Person>("first_name"_c) | order_by("id"_c))
.value();
using namespace std::ranges::views;
const auto first_names =
internal::collect::vector(people2 | transform([](const auto& _r) {
return rfl::get<"first_name">(_r.value());
}));
EXPECT_EQ(first_names.at(0), "Homer");
EXPECT_EQ(first_names.at(1), "Bart");
EXPECT_EQ(first_names.at(2), "Lisa");
EXPECT_EQ(first_names.at(3), "Maggie");
}
} // namespace test_range_select_from

View File

@@ -0,0 +1,51 @@
#include <gtest/gtest.h>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_range_select_from_with_to {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_range_select_from_with_to) {
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;
using namespace sqlgen::literals;
struct FirstName {
std::string first_name;
};
const auto people2 =
duckdb::connect()
.and_then(drop<Person> | if_exists)
.and_then(write(std::ref(people1)))
.and_then(select_from<Person>("first_name"_c) | order_by("id"_c) |
to<std::vector<FirstName>>)
.value();
EXPECT_EQ(people2.at(0).first_name, "Homer");
EXPECT_EQ(people2.at(1).first_name, "Bart");
EXPECT_EQ(people2.at(2).first_name, "Lisa");
EXPECT_EQ(people2.at(3).first_name, "Maggie");
}
} // namespace test_range_select_from_with_to

View File

@@ -0,0 +1,69 @@
#include <gtest/gtest.h>
#include <optional>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_right_join {
TEST(duckdb, test_right_join) {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
};
struct Pet {
sqlgen::PrimaryKey<uint32_t> id;
std::string name;
uint32_t owner_id;
};
const auto people = std::vector<Person>({
Person{.id = 1, .first_name = "Homer", .last_name = "Simpson"},
Person{.id = 2, .first_name = "Marge", .last_name = "Simpson"},
Person{.id = 3, .first_name = "Bart", .last_name = "Simpson"},
Person{.id = 4, .first_name = "Lisa", .last_name = "Simpson"},
});
const auto pets = std::vector<Pet>({
Pet{.id = 1, .name = "Santa's Little Helper", .owner_id = 1},
Pet{.id = 2, .name = "Snowball", .owner_id = 4},
Pet{.id = 3, .name = "Mr. Teeny", .owner_id = 99},
});
using namespace sqlgen;
using namespace sqlgen::literals;
struct PetWithOwner {
std::string pet_name;
std::optional<std::string> owner_first_name;
std::optional<std::string> owner_last_name;
};
const auto get_pets_with_owners =
select_from<Person, "t1">("name"_t2 | as<"pet_name">,
"first_name"_t1 | as<"owner_first_name">,
"last_name"_t1 | as<"owner_last_name">) |
right_join<Pet, "t2">("id"_t1 == "owner_id"_t2) | order_by("id"_t2) |
to<std::vector<PetWithOwner>>;
const auto result = duckdb::connect()
.and_then(write(std::ref(people)))
.and_then(write(std::ref(pets)))
.and_then(get_pets_with_owners)
.value();
const std::string expected_query =
R"(SELECT t2."name" AS "pet_name", t1."first_name" AS "owner_first_name", t1."last_name" AS "owner_last_name" FROM "Person" t1 RIGHT JOIN "Pet" t2 ON t1."id" = t2."owner_id" ORDER BY t2."id")";
const std::string expected_json =
R"([{"pet_name":"Santa's Little Helper","owner_first_name":"Homer","owner_last_name":"Simpson"},{"pet_name":"Snowball","owner_first_name":"Lisa","owner_last_name":"Simpson"},{"pet_name":"Mr. Teeny"}])";
EXPECT_EQ(duckdb::to_sql(get_pets_with_owners), expected_query);
EXPECT_EQ(rfl::json::write(result), expected_json);
}
} // namespace test_right_join

View File

@@ -0,0 +1,79 @@
#include <gtest/gtest.h>
#include <chrono>
#include <ranges>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_range_select_from_with_timestamps {
struct Person {
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
std::string first_name;
std::string last_name;
sqlgen::Date birthday;
};
TEST(duckdb, test_range_select_from_with_timestamps) {
const auto people1 =
std::vector<Person>({Person{.first_name = "Homer",
.last_name = "Simpson",
.birthday = sqlgen::Date("1970-01-01")},
Person{.first_name = "Bart",
.last_name = "Simpson",
.birthday = sqlgen::Date("2000-01-01")},
Person{.first_name = "Lisa",
.last_name = "Simpson",
.birthday = sqlgen::Date("2002-01-01")},
Person{.first_name = "Maggie",
.last_name = "Simpson",
.birthday = sqlgen::Date("2010-01-01")}});
using namespace sqlgen;
using namespace sqlgen::literals;
struct Birthday {
Date birthday;
Date birthday_recreated;
time_t birthday_unixepoch;
double age_in_days;
int hour;
int minute;
int second;
int weekday;
};
const auto get_birthdays =
select_from<Person>(
("birthday"_c + std::chrono::days(10)) | as<"birthday">,
((cast<Date>(concat(cast<std::string>(year("birthday"_c)), "-",
cast<std::string>(month("birthday"_c)), "-",
cast<std::string>(day("birthday"_c)))))) |
as<"birthday_recreated">,
days_between("birthday"_c, Date("2011-01-01")) | as<"age_in_days">,
unixepoch("birthday"_c + std::chrono::days(10)) |
as<"birthday_unixepoch">,
hour("birthday"_c) | as<"hour">, minute("birthday"_c) | as<"minute">,
second("birthday"_c) | as<"second">,
weekday("birthday"_c) | as<"weekday">) |
order_by("id"_c) | to<std::vector<Birthday>>;
const auto birthdays = duckdb::connect()
.and_then(write(std::ref(people1)))
.and_then(get_birthdays)
.value();
const std::string expected_query =
R"(SELECT "birthday" + INTERVAL '10 days' AS "birthday", cast((cast(extract(YEAR from "birthday") as TEXT) || '-' || cast(extract(MONTH from "birthday") as TEXT) || '-' || cast(extract(DAY from "birthday") as TEXT)) as DATE) AS "birthday_recreated", cast('2011-01-01' as DATE) - cast("birthday" as DATE) AS "age_in_days", extract(EPOCH FROM "birthday" + INTERVAL '10 days') AS "birthday_unixepoch", extract(HOUR from "birthday") AS "hour", extract(MINUTE from "birthday") AS "minute", extract(SECOND from "birthday") AS "second", extract(DOW from "birthday") AS "weekday" FROM "Person" ORDER BY "id")";
const std::string expected =
R"([{"birthday":"1970-01-11","birthday_recreated":"1970-01-01","birthday_unixepoch":864000,"age_in_days":14975.0,"hour":0,"minute":0,"second":0,"weekday":4},{"birthday":"2000-01-11","birthday_recreated":"2000-01-01","birthday_unixepoch":947548800,"age_in_days":4018.0,"hour":0,"minute":0,"second":0,"weekday":6},{"birthday":"2002-01-11","birthday_recreated":"2002-01-01","birthday_unixepoch":1010707200,"age_in_days":3287.0,"hour":0,"minute":0,"second":0,"weekday":2},{"birthday":"2010-01-11","birthday_recreated":"2010-01-01","birthday_unixepoch":1263168000,"age_in_days":365.0,"hour":0,"minute":0,"second":0,"weekday":5}])";
EXPECT_EQ(duckdb::to_sql(get_birthdays), expected_query);
EXPECT_EQ(rfl::json::write(birthdays), expected);
}
} // namespace test_range_select_from_with_timestamps

View File

@@ -0,0 +1,43 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_single_read {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};
TEST(duckdb, test_single_read) {
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 conn = sqlgen::duckdb::connect();
sqlgen::write(conn, people1);
using namespace sqlgen;
using namespace sqlgen::literals;
const auto people2 =
(sqlgen::read<Person> | where("id"_c == 0))(conn).value();
const auto json1 = rfl::json::write(people1.at(0));
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_single_read

View File

@@ -0,0 +1,49 @@
#include <gtest/gtest.h>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <vector>
namespace test_timestamp {
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
sqlgen::Timestamp<"%Y-%m-%d %H:%M:%S"> birthdate;
};
TEST(duckdb, test_timestamp) {
const auto people1 =
std::vector<Person>({Person{.id = 0,
.first_name = "Homer",
.last_name = "Simpson",
.birthdate = "1989-12-17 12:00:00"},
Person{.id = 1,
.first_name = "Bart",
.last_name = "Simpson",
.birthdate = "1989-12-17 12:00:00"},
Person{.id = 2,
.first_name = "Lisa",
.last_name = "Simpson",
.birthdate = "1989-12-17 12:00:00"},
Person{.id = 3,
.first_name = "Maggie",
.last_name = "Simpson",
.birthdate = "1989-12-17 12:00:00"}});
const auto conn = sqlgen::duckdb::connect();
sqlgen::write(conn, people1);
const auto people2 = sqlgen::read<std::vector<Person>>(conn).value();
const auto json1 = rfl::json::write(people1);
const auto json2 = rfl::json::write(people2);
EXPECT_EQ(json1, json2);
}
} // namespace test_timestamp

View File

@@ -0,0 +1,25 @@
#include <gtest/gtest.h>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <sqlgen/transpilation/to_create_table.hpp>
namespace test_to_create_table {
struct TestTable {
std::string field1;
int32_t field2;
sqlgen::PrimaryKey<uint32_t> id;
std::optional<std::string> nullable;
};
TEST(duckdb, test_to_create_table) {
const auto create_table_stmt =
sqlgen::transpilation::to_create_table<TestTable>();
const auto conn = sqlgen::duckdb::connect().value();
const auto expected =
R"(CREATE TABLE IF NOT EXISTS "TestTable"("field1" TEXT NOT NULL, "field2" INTEGER NOT NULL, "id" UINTEGER NOT NULL, "nullable" TEXT, PRIMARY KEY ("id"));)";
EXPECT_EQ(conn->to_sql(create_table_stmt), expected);
}
} // namespace test_to_create_table

View File

@@ -0,0 +1,27 @@
#include <gtest/gtest.h>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <sqlgen/dynamic/Insert.hpp>
#include <sqlgen/transpilation/to_insert_or_write.hpp>
namespace test_to_insert {
struct TestTable {
std::string field1;
int32_t field2;
sqlgen::PrimaryKey<uint32_t> id;
std::optional<std::string> nullable;
};
TEST(duckdb, test_to_insert) {
const auto insert_stmt =
sqlgen::transpilation::to_insert_or_write<TestTable,
sqlgen::dynamic::Insert>();
const auto conn = sqlgen::duckdb::connect().value();
const auto expected =
R"(INSERT INTO "TestTable" BY NAME ( SELECT "field1" AS "field1", "field2" AS "field2", "id" AS "id", "nullable" AS "nullable" FROM sqlgen_appended_data);)";
EXPECT_EQ(conn->to_sql(insert_stmt), expected);
}
} // namespace test_to_insert

View File

@@ -0,0 +1,33 @@
#include <gtest/gtest.h>
#include <sqlgen.hpp>
#include <sqlgen/duckdb.hpp>
#include <sqlgen/dynamic/Insert.hpp>
#include <sqlgen/transpilation/to_insert_or_write.hpp>
namespace test_to_insert_or_replace {
struct TestTable {
std::string field1;
int32_t field2;
sqlgen::Unique<std::string> field3;
sqlgen::PrimaryKey<uint32_t> id;
std::optional<std::string> nullable;
};
TEST(duckdb, test_to_insert_or_replace) {
static_assert(sqlgen::internal::has_constraint_v<TestTable>,
"The table must have a primary key or unique column for "
"insert_or_replace(...) to work.");
const auto insert_stmt =
sqlgen::transpilation::to_insert_or_write<TestTable,
sqlgen::dynamic::Insert>(true);
const auto conn = sqlgen::duckdb::connect().value();
const auto expected =
R"(INSERT OR REPLACE INTO "TestTable" BY NAME ( SELECT "field1" AS "field1", "field2" AS "field2", "field3" AS "field3", "id" AS "id", "nullable" AS "nullable" FROM sqlgen_appended_data);)";
EXPECT_EQ(conn->to_sql(insert_stmt), expected);
}
} // namespace test_to_insert_or_replace

Some files were not shown because too many files have changed in this diff Show More