mirror of
https://github.com/getml/sqlgen.git
synced 2026-01-05 17:09:50 -06:00
Added support for DuckDB (#90)
This commit is contained in:
committed by
GitHub
parent
e07be25a5d
commit
8568a83362
7
.github/workflows/windows-cxx20-vcpkg.yaml
vendored
7
.github/workflows/windows-cxx20-vcpkg.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 | |
|
||||
|
||||
@@ -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
142
docs/duckdb.md
Normal 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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
8
include/sqlgen/duckdb.hpp
Normal file
8
include/sqlgen/duckdb.hpp
Normal 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
|
||||
33
include/sqlgen/duckdb/ColumnData.hpp
Normal file
33
include/sqlgen/duckdb/ColumnData.hpp
Normal 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
|
||||
209
include/sqlgen/duckdb/Connection.hpp
Normal file
209
include/sqlgen/duckdb/Connection.hpp
Normal 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
|
||||
84
include/sqlgen/duckdb/DuckDBAppender.hpp
Normal file
84
include/sqlgen/duckdb/DuckDBAppender.hpp
Normal 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
|
||||
52
include/sqlgen/duckdb/DuckDBConnection.hpp
Normal file
52
include/sqlgen/duckdb/DuckDBConnection.hpp
Normal 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
|
||||
69
include/sqlgen/duckdb/DuckDBResult.hpp
Normal file
69
include/sqlgen/duckdb/DuckDBResult.hpp
Normal 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
|
||||
113
include/sqlgen/duckdb/Iterator.hpp
Normal file
113
include/sqlgen/duckdb/Iterator.hpp
Normal 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
|
||||
103
include/sqlgen/duckdb/cast_duckdb_type.hpp
Normal file
103
include/sqlgen/duckdb/cast_duckdb_type.hpp
Normal 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
|
||||
68
include/sqlgen/duckdb/check_duckdb_type.hpp
Normal file
68
include/sqlgen/duckdb/check_duckdb_type.hpp
Normal 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
|
||||
32
include/sqlgen/duckdb/chunk_ptrs_t.hpp
Normal file
32
include/sqlgen/duckdb/chunk_ptrs_t.hpp
Normal 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
|
||||
16
include/sqlgen/duckdb/connect.hpp
Normal file
16
include/sqlgen/duckdb/connect.hpp
Normal 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
|
||||
49
include/sqlgen/duckdb/from_chunk_ptrs.hpp
Normal file
49
include/sqlgen/duckdb/from_chunk_ptrs.hpp
Normal 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
|
||||
90
include/sqlgen/duckdb/make_chunk_ptrs.hpp
Normal file
90
include/sqlgen/duckdb/make_chunk_ptrs.hpp
Normal 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
|
||||
15
include/sqlgen/duckdb/parsing/Parser.hpp
Normal file
15
include/sqlgen/duckdb/parsing/Parser.hpp
Normal 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
|
||||
11
include/sqlgen/duckdb/parsing/Parser_base.hpp
Normal file
11
include/sqlgen/duckdb/parsing/Parser_base.hpp
Normal 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
|
||||
42
include/sqlgen/duckdb/parsing/Parser_date.hpp
Normal file
42
include/sqlgen/duckdb/parsing/Parser_date.hpp
Normal 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
|
||||
94
include/sqlgen/duckdb/parsing/Parser_default.hpp
Normal file
94
include/sqlgen/duckdb/parsing/Parser_default.hpp
Normal 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
|
||||
44
include/sqlgen/duckdb/parsing/Parser_enum.hpp
Normal file
44
include/sqlgen/duckdb/parsing/Parser_enum.hpp
Normal 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
|
||||
39
include/sqlgen/duckdb/parsing/Parser_json.hpp
Normal file
39
include/sqlgen/duckdb/parsing/Parser_json.hpp
Normal 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
|
||||
43
include/sqlgen/duckdb/parsing/Parser_optional.hpp
Normal file
43
include/sqlgen/duckdb/parsing/Parser_optional.hpp
Normal 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
|
||||
36
include/sqlgen/duckdb/parsing/Parser_reflection_type.hpp
Normal file
36
include/sqlgen/duckdb/parsing/Parser_reflection_type.hpp
Normal 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
|
||||
46
include/sqlgen/duckdb/parsing/Parser_smart_ptr.hpp
Normal file
46
include/sqlgen/duckdb/parsing/Parser_smart_ptr.hpp
Normal 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
|
||||
40
include/sqlgen/duckdb/parsing/Parser_string.hpp
Normal file
40
include/sqlgen/duckdb/parsing/Parser_string.hpp
Normal 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
|
||||
40
include/sqlgen/duckdb/parsing/Parser_timestamp.hpp
Normal file
40
include/sqlgen/duckdb/parsing/Parser_timestamp.hpp
Normal 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
|
||||
27
include/sqlgen/duckdb/to_sql.hpp
Normal file
27
include/sqlgen/duckdb/to_sql.hpp
Normal 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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
40
src/sqlgen/duckdb/Connection.cpp
Normal file
40
src/sqlgen/duckdb/Connection.cpp
Normal 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
|
||||
43
src/sqlgen/duckdb/DuckDBConnection.cpp
Normal file
43
src/sqlgen/duckdb/DuckDBConnection.cpp
Normal 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
|
||||
27
src/sqlgen/duckdb/exec.cpp
Normal file
27
src/sqlgen/duckdb/exec.cpp
Normal 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
|
||||
960
src/sqlgen/duckdb/to_sql.cpp
Normal file
960
src/sqlgen/duckdb/to_sql.cpp
Normal 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
5
src/sqlgen_duckdb.cpp
Normal 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"
|
||||
@@ -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()
|
||||
|
||||
19
tests/duckdb/CMakeLists.txt
Normal file
19
tests/duckdb/CMakeLists.txt
Normal 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)
|
||||
64
tests/duckdb/test_aggregations.cpp
Normal file
64
tests/duckdb/test_aggregations.cpp
Normal 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
|
||||
|
||||
63
tests/duckdb/test_aggregations_with_nullable.cpp
Normal file
63
tests/duckdb/test_aggregations_with_nullable.cpp
Normal 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
|
||||
|
||||
39
tests/duckdb/test_alpha_numeric.cpp
Normal file
39
tests/duckdb/test_alpha_numeric.cpp
Normal 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
|
||||
47
tests/duckdb/test_alpha_numeric_query.cpp
Normal file
47
tests/duckdb/test_alpha_numeric_query.cpp
Normal 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
|
||||
45
tests/duckdb/test_auto_incr_primary_key.cpp
Normal file
45
tests/duckdb/test_auto_incr_primary_key.cpp
Normal 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
|
||||
52
tests/duckdb/test_boolean.cpp
Normal file
52
tests/duckdb/test_boolean.cpp
Normal 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
|
||||
|
||||
54
tests/duckdb/test_boolean_conditions.cpp
Normal file
54
tests/duckdb/test_boolean_conditions.cpp
Normal 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
|
||||
|
||||
59
tests/duckdb/test_boolean_update.cpp
Normal file
59
tests/duckdb/test_boolean_update.cpp
Normal 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
|
||||
|
||||
45
tests/duckdb/test_cache.cpp
Normal file
45
tests/duckdb/test_cache.cpp
Normal 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
|
||||
34
tests/duckdb/test_create_index.cpp
Normal file
34
tests/duckdb/test_create_index.cpp
Normal 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
|
||||
31
tests/duckdb/test_create_table.cpp
Normal file
31
tests/duckdb/test_create_table.cpp
Normal 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
|
||||
60
tests/duckdb/test_create_table_as.cpp
Normal file
60
tests/duckdb/test_create_table_as.cpp
Normal 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
|
||||
|
||||
65
tests/duckdb/test_create_view_as.cpp
Normal file
65
tests/duckdb/test_create_view_as.cpp
Normal 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
|
||||
|
||||
48
tests/duckdb/test_delete_from.cpp
Normal file
48
tests/duckdb/test_delete_from.cpp
Normal 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
|
||||
41
tests/duckdb/test_drop.cpp
Normal file
41
tests/duckdb/test_drop.cpp
Normal 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
|
||||
129
tests/duckdb/test_dynamic_type.cpp
Normal file
129
tests/duckdb/test_dynamic_type.cpp
Normal 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
|
||||
|
||||
100
tests/duckdb/test_enum_crosstable.cpp
Normal file
100
tests/duckdb/test_enum_crosstable.cpp
Normal 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
|
||||
|
||||
84
tests/duckdb/test_enum_lookup.cpp
Normal file
84
tests/duckdb/test_enum_lookup.cpp
Normal 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
|
||||
|
||||
59
tests/duckdb/test_enum_namespace.cpp
Normal file
59
tests/duckdb/test_enum_namespace.cpp
Normal 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
|
||||
|
||||
45
tests/duckdb/test_flatten.cpp
Normal file
45
tests/duckdb/test_flatten.cpp
Normal 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
|
||||
58
tests/duckdb/test_foreign_key.cpp
Normal file
58
tests/duckdb/test_foreign_key.cpp
Normal 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
|
||||
|
||||
69
tests/duckdb/test_full_join.cpp
Normal file
69
tests/duckdb/test_full_join.cpp
Normal 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
|
||||
67
tests/duckdb/test_group_by.cpp
Normal file
67
tests/duckdb/test_group_by.cpp
Normal 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
|
||||
|
||||
63
tests/duckdb/test_group_by_with_operations.cpp
Normal file
63
tests/duckdb/test_group_by_with_operations.cpp
Normal 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
|
||||
|
||||
32
tests/duckdb/test_hello_world.cpp
Normal file
32
tests/duckdb/test_hello_world.cpp
Normal 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
48
tests/duckdb/test_in.cpp
Normal 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
|
||||
|
||||
49
tests/duckdb/test_in_vec.cpp
Normal file
49
tests/duckdb/test_in_vec.cpp
Normal 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
|
||||
|
||||
44
tests/duckdb/test_insert_and_read.cpp
Normal file
44
tests/duckdb/test_insert_and_read.cpp
Normal 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
|
||||
44
tests/duckdb/test_insert_by_ref_and_read.cpp
Normal file
44
tests/duckdb/test_insert_by_ref_and_read.cpp
Normal 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
|
||||
57
tests/duckdb/test_insert_fail.cpp
Normal file
57
tests/duckdb/test_insert_fail.cpp
Normal 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
|
||||
70
tests/duckdb/test_insert_or_replace.cpp
Normal file
70
tests/duckdb/test_insert_or_replace.cpp
Normal 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
|
||||
55
tests/duckdb/test_is_null.cpp
Normal file
55
tests/duckdb/test_is_null.cpp
Normal 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
|
||||
|
||||
51
tests/duckdb/test_join.cpp
Normal file
51
tests/duckdb/test_join.cpp
Normal 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
|
||||
86
tests/duckdb/test_joins_from.cpp
Normal file
86
tests/duckdb/test_joins_from.cpp
Normal 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
|
||||
|
||||
82
tests/duckdb/test_joins_nested.cpp
Normal file
82
tests/duckdb/test_joins_nested.cpp
Normal 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
|
||||
81
tests/duckdb/test_joins_nested_grouped.cpp
Normal file
81
tests/duckdb/test_joins_nested_grouped.cpp
Normal 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
|
||||
77
tests/duckdb/test_joins_two_tables.cpp
Normal file
77
tests/duckdb/test_joins_two_tables.cpp
Normal 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
|
||||
78
tests/duckdb/test_joins_two_tables_grouped.cpp
Normal file
78
tests/duckdb/test_joins_two_tables_grouped.cpp
Normal 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
|
||||
45
tests/duckdb/test_json.cpp
Normal file
45
tests/duckdb/test_json.cpp
Normal 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
|
||||
|
||||
69
tests/duckdb/test_left_join.cpp
Normal file
69
tests/duckdb/test_left_join.cpp
Normal 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
|
||||
65
tests/duckdb/test_like.cpp
Normal file
65
tests/duckdb/test_like.cpp
Normal 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
|
||||
|
||||
47
tests/duckdb/test_limit.cpp
Normal file
47
tests/duckdb/test_limit.cpp
Normal 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
|
||||
48
tests/duckdb/test_not_in.cpp
Normal file
48
tests/duckdb/test_not_in.cpp
Normal 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
|
||||
|
||||
49
tests/duckdb/test_not_in_vec.cpp
Normal file
49
tests/duckdb/test_not_in_vec.cpp
Normal 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
|
||||
|
||||
76
tests/duckdb/test_operations.cpp
Normal file
76
tests/duckdb/test_operations.cpp
Normal 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
|
||||
|
||||
63
tests/duckdb/test_operations_with_nullable.cpp
Normal file
63
tests/duckdb/test_operations_with_nullable.cpp
Normal 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
|
||||
|
||||
47
tests/duckdb/test_order_by.cpp
Normal file
47
tests/duckdb/test_order_by.cpp
Normal 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
|
||||
47
tests/duckdb/test_range.cpp
Normal file
47
tests/duckdb/test_range.cpp
Normal 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
|
||||
52
tests/duckdb/test_range_select_from.cpp
Normal file
52
tests/duckdb/test_range_select_from.cpp
Normal 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
|
||||
|
||||
51
tests/duckdb/test_range_select_from_with_to.cpp
Normal file
51
tests/duckdb/test_range_select_from_with_to.cpp
Normal 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
|
||||
|
||||
69
tests/duckdb/test_right_join.cpp
Normal file
69
tests/duckdb/test_right_join.cpp
Normal 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
|
||||
79
tests/duckdb/test_select_from_with_timestamps.cpp
Normal file
79
tests/duckdb/test_select_from_with_timestamps.cpp
Normal 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
|
||||
|
||||
43
tests/duckdb/test_single_read.cpp
Normal file
43
tests/duckdb/test_single_read.cpp
Normal 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
|
||||
49
tests/duckdb/test_timestamp.cpp
Normal file
49
tests/duckdb/test_timestamp.cpp
Normal 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
|
||||
25
tests/duckdb/test_to_create_table.cpp
Normal file
25
tests/duckdb/test_to_create_table.cpp
Normal 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
|
||||
27
tests/duckdb/test_to_insert.cpp
Normal file
27
tests/duckdb/test_to_insert.cpp
Normal 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
|
||||
33
tests/duckdb/test_to_insert_or_replace.cpp
Normal file
33
tests/duckdb/test_to_insert_or_replace.cpp
Normal 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
Reference in New Issue
Block a user