diff --git a/.github/workflows/linux-cxx20-conan.yaml b/.github/workflows/linux-cxx20-conan.yaml deleted file mode 100644 index 1395061b..00000000 --- a/.github/workflows/linux-cxx20-conan.yaml +++ /dev/null @@ -1,73 +0,0 @@ -name: linux-cxx20-conan - -on: [push, pull_request] - - -jobs: - linux: - strategy: - fail-fast: false - matrix: - include: - - compiler: llvm - compiler-version: 16 - link: "static" - - compiler: llvm - compiler-version: 18 - link: "static" - - compiler: gcc - compiler-version: 11 - additional-dep: "g++-11" - link: "static" - - compiler: gcc - compiler-version: 12 - link: "static" - - compiler: llvm - compiler-version: 16 - link: "shared" - - compiler: llvm - compiler-version: 18 - link: "shared" - - compiler: gcc - compiler-version: 11 - additional-dep: "g++-11" - link: "shared" - - compiler: gcc - compiler-version: 12 - link: "shared" - name: "${{ github.job }} (${{ matrix.compiler }}-${{ matrix.compiler-version }}-${{ matrix.link }})" - concurrency: - group: ci-${{ github.ref }}-${{ github.job }}-${{ matrix.compiler }}-${{ matrix.compiler-version }}-${{ matrix.link }} - cancel-in-progress: true - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - fetch-depth: 0 - - name: Install dependencies - run: | - sudo apt update - sudo apt install -y ninja-build pipx ${{ matrix.additional-dep }} - - name: Install Conan - run: | - pipx install conan - conan profile detect - - name: Compile - run: | - if [[ "${{ matrix.compiler }}" == "llvm" ]]; then - export CC=clang-${{ matrix.compiler-version }} - export CXX=clang++-${{ matrix.compiler-version }} - elif [[ "${{ matrix.compiler }}" == "gcc" ]]; then - export CC=gcc-${{ matrix.compiler-version }} - export CXX=g++-${{ matrix.compiler-version }} - fi - sudo ln -s $(which ccache) /usr/local/bin/$CC - sudo ln -s $(which ccache) /usr/local/bin/$CXX - $CXX --version - if [[ "${{ matrix.link }}" == "static" ]]; then - conan build . --build=missing -s compiler.cppstd=gnu20 - else - conan build . --build=missing -s compiler.cppstd=gnu20 -o sqlgen/*:with_mysql=True -o */*:shared=True - fi diff --git a/.github/workflows/macos-cxx20-conan.yaml b/.github/workflows/macos-cxx20-conan.yaml deleted file mode 100644 index b01bb416..00000000 --- a/.github/workflows/macos-cxx20-conan.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: macos-cxx20-conan - -on: [push, pull_request] - - -jobs: - macos-clang: - strategy: - fail-fast: false - matrix: - include: - - os: "macos-latest" - link: "static" - - os: "macos-13" - link: "static" - - os: "macos-latest" - link: "shared" - - os: "macos-13" - link: "shared" - name: "${{ github.job }} (${{ matrix.os }}-${{ matrix.link }})" - concurrency: - group: ci-${{ github.ref }}-${{ github.job }}-${{ matrix.os }}-${{ matrix.link }} - cancel-in-progress: true - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - fetch-depth: 0 - - name: Install dependencies - run: brew install ninja pipx - - name: Install Conan - run: | - pipx install conan - conan profile detect - - name: Compile - env: - CC: clang - CXX: clang++ - run: | - $CXX --version - if [[ "${{ matrix.link }}" == "static" ]]; then - conan build . --build=missing -s compiler.cppstd=gnu20 - else - conan build . --build=missing -s compiler.cppstd=gnu20 -o sqlgen/*:with_mysql=True -o */*:shared=True - fi diff --git a/docs/README.md b/docs/README.md index e1e079ac..b5a5624b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,6 +29,7 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab - [sqlgen::inner_join, sqlgen::left_join, sqlgen::right_join, sqlgen::full_join](joins.md) - How to join different tables - [sqlgen::insert](insert.md) - How to insert data within transactions - [sqlgen::select_from](select_from.md) - How to read data from a database using more complex queries +- [sqlgen::unite and sqlgen::unite_all](unite.md) - How to combine results from multiple SELECT statements - [sqlgen::update](update.md) - How to update data in a table ## Other Operations diff --git a/docs/unite.md b/docs/unite.md new file mode 100644 index 00000000..6a3f7d5d --- /dev/null +++ b/docs/unite.md @@ -0,0 +1,80 @@ +# `unite` and `unite_all` + +The `unite` and `unite_all` functions allow you to combine the results of multiple `SELECT` statements into a single result set. + +## `unite` + +The `unite` function corresponds to the SQL `UNION` operator. It combines the result sets of two or more `SELECT` statements and removes duplicate rows. + +### Example + +```cpp +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +const auto s1 = sqlgen::select_from("name"_c, "age"_c); +const auto s2 = sqlgen::select_from("name"_c, "age"_c); + +const auto united = sqlgen::unite>(s1, s2); +``` + +## `unite_all` + +The `unite_all` function corresponds to the SQL `UNION ALL` operator. It combines the result sets of two or more `SELECT` statements, including all duplicate rows. + +### Example + +```cpp +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +const auto s1 = sqlgen::select_from("name"_c, "age"_c); +const auto s2 = sqlgen::select_from("name"_c, "age"_c); + +const auto united = sqlgen::unite_all>(s1, s2); +``` + +## Nesting in `SELECT` statements + +You can use the result of a `unite` or `unite_all` operation as a subquery in a `SELECT` statement. + +### Example + +```cpp +const auto united = sqlgen::unite>(s1, s2); + +const auto sel = sqlgen::select_from(united.as("u"), "name"_c, "age"_c); +``` + +## Nesting in `JOIN` statements + +You can also use the result of a `unite` or `unite_all` operation as a subquery in a `JOIN` statement. + +### Example + +```cpp +struct Login { + int id; + std::string username; +}; + +const auto united = sqlgen::unite>(s1, s2); + +const auto sel = select_from("id"_t1, "username"_t1) | + inner_join<"t2">(united, "username"_t1 == "name"_t2) | + where("id"_t1 == 1) | to>; +``` diff --git a/include/sqlgen.hpp b/include/sqlgen.hpp index c9faeeba..d79a4f5d 100644 --- a/include/sqlgen.hpp +++ b/include/sqlgen.hpp @@ -45,6 +45,7 @@ #include "sqlgen/select_from.hpp" #include "sqlgen/sqlgen_api.hpp" #include "sqlgen/to.hpp" +#include "sqlgen/unite.hpp" #include "sqlgen/update.hpp" #include "sqlgen/where.hpp" #include "sqlgen/write.hpp" diff --git a/include/sqlgen/duckdb/Connection.hpp b/include/sqlgen/duckdb/Connection.hpp index 95eff625..19d9b7af 100644 --- a/include/sqlgen/duckdb/Connection.hpp +++ b/include/sqlgen/duckdb/Connection.hpp @@ -72,10 +72,11 @@ class SQLGEN_API Connection { } template - auto read(const dynamic::SelectFrom &_query) { + auto read(const rfl::Variant &_query) { using ValueType = transpilation::value_t; + const auto sql = _query.visit([&](const auto &_q) { return to_sql(_q); }); return internal::to_container>( - Iterator(to_sql(_query), conn_)); + Iterator(sql, conn_)); } Result rollback() noexcept; diff --git a/include/sqlgen/dynamic/SelectFrom.hpp b/include/sqlgen/dynamic/SelectFrom.hpp index c0ee22a0..31d3e742 100644 --- a/include/sqlgen/dynamic/SelectFrom.hpp +++ b/include/sqlgen/dynamic/SelectFrom.hpp @@ -18,7 +18,13 @@ namespace sqlgen::dynamic { struct SelectFrom { - using TableOrQueryType = rfl::Variant>; + struct Union { + std::vector columns; + Ref> selects; + bool all = false; + }; + + using TableOrQueryType = rfl::Variant, Ref>; struct Field { Operation val; diff --git a/include/sqlgen/dynamic/Statement.hpp b/include/sqlgen/dynamic/Statement.hpp index 63226e05..63b97f09 100644 --- a/include/sqlgen/dynamic/Statement.hpp +++ b/include/sqlgen/dynamic/Statement.hpp @@ -10,6 +10,7 @@ #include "Drop.hpp" #include "Insert.hpp" #include "SelectFrom.hpp" +#include "Union.hpp" #include "Update.hpp" #include "Write.hpp" @@ -17,7 +18,7 @@ namespace sqlgen::dynamic { using Statement = rfl::TaggedUnion<"stmt", CreateAs, CreateIndex, CreateTable, DeleteFrom, - Drop, Insert, SelectFrom, Update, Write>; + Drop, Insert, SelectFrom, Union, Update, Write>; } // namespace sqlgen::dynamic diff --git a/include/sqlgen/dynamic/Union.hpp b/include/sqlgen/dynamic/Union.hpp new file mode 100644 index 00000000..47199a9c --- /dev/null +++ b/include/sqlgen/dynamic/Union.hpp @@ -0,0 +1,12 @@ +#ifndef SQLGEN_DYNAMIC_UNION_HPP_ +#define SQLGEN_DYNAMIC_UNION_HPP_ + +#include "SelectFrom.hpp" + +namespace sqlgen::dynamic { + +using Union = SelectFrom::Union; + +} // namespace sqlgen::dynamic + +#endif diff --git a/include/sqlgen/internal/GetColType.hpp b/include/sqlgen/internal/GetColType.hpp index bd4ea4ac..da52ff8b 100644 --- a/include/sqlgen/internal/GetColType.hpp +++ b/include/sqlgen/internal/GetColType.hpp @@ -17,12 +17,18 @@ struct GetColType { static Type get_value(const T& _t) { return _t; } }; -template -struct GetColType> { - using Type = transpilation::Col<_name>; - static Type get_value(const auto&) { return transpilation::Col<_name>{}; } +template +struct GetColType> { + using Type = transpilation::Col<_name, _alias>; + static Type get_value(const auto&) { + return transpilation::Col<_name, _alias>{}; + } }; +template +using get_col_type_t = typename GetColType::Type; + } // namespace sqlgen::internal #endif diff --git a/include/sqlgen/internal/all_same_v.hpp b/include/sqlgen/internal/all_same_v.hpp new file mode 100644 index 00000000..4ac2e8e6 --- /dev/null +++ b/include/sqlgen/internal/all_same_v.hpp @@ -0,0 +1,23 @@ +#include +#include +#include + +namespace sqlgen::internal { + +template +struct AllSame; + +template +struct AllSame> { + static constexpr bool value = std::conjunction_v...>; +}; + +template +struct AllSame> { + static constexpr bool value = std::conjunction_v...>; +}; + +template +constexpr bool all_same_v = AllSame>::value; + +} // namespace sqlgen::internal diff --git a/include/sqlgen/mysql/Connection.hpp b/include/sqlgen/mysql/Connection.hpp index 760edf93..4bf62d5a 100644 --- a/include/sqlgen/mysql/Connection.hpp +++ b/include/sqlgen/mysql/Connection.hpp @@ -13,7 +13,10 @@ #include "../Result.hpp" #include "../Transaction.hpp" #include "../dynamic/Column.hpp" +#include "../dynamic/Insert.hpp" +#include "../dynamic/SelectFrom.hpp" #include "../dynamic/Statement.hpp" +#include "../dynamic/Union.hpp" #include "../dynamic/Write.hpp" #include "../internal/to_container.hpp" #include "../internal/write_or_insert.hpp" @@ -54,7 +57,7 @@ class SQLGEN_API Connection { } template - auto read(const dynamic::SelectFrom& _query) { + auto read(const rfl::Variant& _query) { using ValueType = transpilation::value_t; return internal::to_container( read_impl(_query).transform([](auto&& _it) { @@ -94,7 +97,8 @@ class SQLGEN_API Connection { const std::variant& _stmt) const noexcept; - Result> read_impl(const dynamic::SelectFrom& _query); + Result> read_impl( + const rfl::Variant& _query); Result write_impl( const std::vector>>& _data); diff --git a/include/sqlgen/postgres/Connection.hpp b/include/sqlgen/postgres/Connection.hpp index 9a31dc16..77924f1d 100644 --- a/include/sqlgen/postgres/Connection.hpp +++ b/include/sqlgen/postgres/Connection.hpp @@ -4,16 +4,21 @@ #include #include +#include #include #include #include +#include #include "../Iterator.hpp" #include "../Ref.hpp" #include "../Result.hpp" #include "../Transaction.hpp" #include "../dynamic/Column.hpp" +#include "../dynamic/Insert.hpp" +#include "../dynamic/SelectFrom.hpp" #include "../dynamic/Statement.hpp" +#include "../dynamic/Union.hpp" #include "../dynamic/Write.hpp" #include "../internal/iterator_t.hpp" #include "../internal/to_container.hpp" @@ -57,7 +62,7 @@ class SQLGEN_API Connection { } template - auto read(const dynamic::SelectFrom& _query) { + auto read(const rfl::Variant& _query) { using ValueType = transpilation::value_t; return internal::to_container( read_impl(_query).transform([](auto&& _it) { @@ -86,7 +91,8 @@ class SQLGEN_API Connection { const std::vector>>& _data) noexcept; - Result> read_impl(const dynamic::SelectFrom& _query); + Result> read_impl( + const rfl::Variant& _query); std::string to_buffer( const std::vector>& _line) const noexcept; diff --git a/include/sqlgen/select_from.hpp b/include/sqlgen/select_from.hpp index 4786afbb..dddc4c30 100644 --- a/include/sqlgen/select_from.hpp +++ b/include/sqlgen/select_from.hpp @@ -32,21 +32,14 @@ namespace sqlgen { -template +template requires is_connection -auto select_from_impl(const Ref& _conn, const FieldsType& _fields, - const TableOrQueryType& _table_or_query, - const JoinsType& _joins, const WhereType& _where, - const LimitType& _limit) { +auto select_from_impl(const Ref& _conn, const auto& _fields, + const auto& _table_or_query, const auto& _joins, + const auto& _where, const auto& _limit) { if constexpr (internal::is_range_v) { - const auto query = - transpilation::to_select_from( - _fields, _table_or_query, _joins, _where, _limit); + const auto query = transpilation::to_select_from( + _fields, _table_or_query, _joins, _where, _limit); return _conn->template read(query); } else { @@ -64,43 +57,51 @@ auto select_from_impl(const Ref& _conn, const FieldsType& _fields, return container; }; - using IteratorType = internal::iterator_t< - transpilation::fields_to_named_tuple_t, - decltype(_conn)>; + using IteratorType = + internal::iterator_t, + decltype(_conn)>; using RangeType = Range; - return select_from_impl( + return select_from_impl( _conn, _fields, _table_or_query, _joins, _where, _limit) .and_then(to_container); } } -template +template requires is_connection -auto select_from_impl(const Result>& _res, - const FieldsType& _fields, - const TableOrQueryType& _table_or_query, - const JoinsType& _joins, const WhereType& _where, - const LimitType& _limit) { +auto select_from_impl(const Result>& _res, const auto& _fields, + const auto& _table_or_query, const auto& _joins, + const auto& _where, const auto& _limit) { return _res.and_then([&](const auto& _conn) { - return select_from_impl( + return select_from_impl( _conn, _fields, _table_or_query, _joins, _where, _limit); }); } -template +template struct SelectFrom { + using TableOrQueryType = TableOrQueryT; + using AliasType = AliasT; + using FieldsType = FieldsT; + using JoinsType = JoinsT; + using WhereType = WhereT; + using GroupByType = GroupByT; + using OrderByType = OrderByT; + using LimitType = LimitT; + using ToType = ToT; + + using SelectFromTypes = + transpilation::SelectFromTypes; + auto operator()(const auto& _conn) const { using TableTupleType = transpilation::table_tuple_t; @@ -113,9 +114,7 @@ struct SelectFrom { using ContainerType = std::conditional_t, Range, ToType>; - return select_from_impl< - TableTupleType, AliasType, FieldsType, TableOrQueryType, JoinsType, - WhereType, GroupByType, OrderByType, LimitType, ContainerType>( + return select_from_impl( _conn, fields_, from_, joins_, where_, limit_); } else { @@ -129,9 +128,7 @@ struct SelectFrom { return std::move(_vec[0]); }; - return select_from_impl>>( _conn, fields_, from_, joins_, where_, limit_) .and_then(extract_result); @@ -311,12 +308,9 @@ struct ToTableOrQuery< SelectFrom> { dynamic::SelectFrom::TableOrQueryType operator()(const auto& _query) { - using TableTupleType = - table_tuple_t; + using QueryType = std::remove_cvref_t; return Ref::make( - transpilation::to_select_from( + transpilation::to_select_from( _query.fields_, _query.from_, _query.joins_, _query.where_, _query.limit_)); } @@ -347,7 +341,7 @@ inline auto select_from(const FieldTypes&... _fields) { .from_ = transpilation::TableWrapper{}}; } -template inline auto select_from(const QueryType& _query, const FieldTypes&... _fields) { using FieldsType = diff --git a/include/sqlgen/sqlite/Connection.hpp b/include/sqlgen/sqlite/Connection.hpp index bc344049..ec04dd08 100644 --- a/include/sqlgen/sqlite/Connection.hpp +++ b/include/sqlgen/sqlite/Connection.hpp @@ -13,6 +13,8 @@ #include "../Ref.hpp" #include "../Result.hpp" #include "../Transaction.hpp" +#include "../dynamic/SelectFrom.hpp" +#include "../dynamic/Union.hpp" #include "../dynamic/Write.hpp" #include "../internal/to_container.hpp" #include "../internal/write_or_insert.hpp" @@ -50,7 +52,7 @@ class SQLGEN_API Connection { } template - auto read(const dynamic::SelectFrom& _query) { + auto read(const rfl::Variant& _query) { using ValueType = transpilation::value_t; return internal::to_container( read_impl(_query).transform([](auto&& _it) { @@ -92,7 +94,8 @@ class SQLGEN_API Connection { Result prepare_statement(const std::string& _sql) const noexcept; /// Implements the actual read. - Result> read_impl(const dynamic::SelectFrom& _query); + Result> read_impl( + const rfl::Variant& _query); /// Implements the actual write Result write_impl( diff --git a/include/sqlgen/transpilation/Union.hpp b/include/sqlgen/transpilation/Union.hpp new file mode 100644 index 00000000..20ddddd5 --- /dev/null +++ b/include/sqlgen/transpilation/Union.hpp @@ -0,0 +1,14 @@ +#ifndef SQLGEN_TRANSPILATION_UNION_HPP_ +#define SQLGEN_TRANSPILATION_UNION_HPP_ + +#include +#include + +namespace sqlgen::transpilation { + +template +struct Union {}; + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/transpilation/fields_to_named_tuple_t.hpp b/include/sqlgen/transpilation/fields_to_named_tuple_t.hpp index 8b2f9ff4..88feb2c4 100644 --- a/include/sqlgen/transpilation/fields_to_named_tuple_t.hpp +++ b/include/sqlgen/transpilation/fields_to_named_tuple_t.hpp @@ -4,7 +4,10 @@ #include #include "../Literal.hpp" +#include "../internal/all_same_v.hpp" +#include "Union.hpp" #include "make_field.hpp" +#include "table_tuple_t.hpp" namespace sqlgen::transpilation { @@ -38,6 +41,32 @@ struct FieldsToNamedTupleType> { using Type = typename FieldsToNamedTupleType::Type; }; +template +struct FieldsToNamedTupleType> { + using NamedTupleTypes = rfl::Tuple, + typename SelectTs::FieldsType>::Type...>; + static_assert( + sqlgen::internal::all_same_v, + "All SELECT statements in a UNION must return the same columns with " + "the same types."); + using Type = rfl::tuple_element_t<0, NamedTupleTypes>; +}; + +template +struct FieldsToNamedTupleType, FieldTypes...> { + using Type = typename FieldsToNamedTupleType< + typename FieldsToNamedTupleType>::Type, + FieldTypes...>::Type; +}; + +template +struct FieldsToNamedTupleType, rfl::Tuple> { + using Type = + typename FieldsToNamedTupleType, FieldTypes...>::Type; +}; + template using fields_to_named_tuple_t = typename FieldsToNamedTupleType::Type; diff --git a/include/sqlgen/transpilation/to_create_as.hpp b/include/sqlgen/transpilation/to_create_as.hpp index 53051e08..55c3ed49 100644 --- a/include/sqlgen/transpilation/to_create_as.hpp +++ b/include/sqlgen/transpilation/to_create_as.hpp @@ -27,15 +27,18 @@ dynamic::CreateAs to_create_as(const dynamic::CreateAs::What _what, const TableOrQueryType& _table_or_query, const JoinsType& _joins, const WhereType& _where, const LimitType& _limit) { + using SelectFromTypes = + transpilation::SelectFromTypes; + return dynamic::CreateAs{ .what = _what, .table_or_view = dynamic::Table{.alias = std::nullopt, .name = get_tablename(), .schema = get_schema()}, - .query = to_select_from( - _fields, _table_or_query, _joins, _where, _limit), + .query = to_select_from(_fields, _table_or_query, _joins, + _where, _limit), .or_replace = _or_replace, .if_not_exists = _if_not_exists}; } diff --git a/include/sqlgen/transpilation/to_select_from.hpp b/include/sqlgen/transpilation/to_select_from.hpp index 08877d1f..d453d5c8 100644 --- a/include/sqlgen/transpilation/to_select_from.hpp +++ b/include/sqlgen/transpilation/to_select_from.hpp @@ -20,6 +20,7 @@ #include "flatten_fields_t.hpp" #include "get_table_t.hpp" #include "make_fields.hpp" +#include "table_tuple_t.hpp" #include "to_alias.hpp" #include "to_condition.hpp" #include "to_group_by.hpp" @@ -30,14 +31,32 @@ namespace sqlgen::transpilation { -template -dynamic::SelectFrom to_select_from(const FieldsType& _fields, - const TableOrQueryType& _table_or_query, - const JoinsType& _joins, - const WhereType& _where, - const LimitType& _limit) { +template +struct SelectFromTypes { + using AliasType = AliasT; + using FieldsType = FieldsT; + using TableOrQueryType = TableOrQueryT; + using JoinsType = JoinsT; + using WhereType = WhereT; + using GroupByType = GroupByT; + using OrderByType = OrderByT; + using LimitType = LimitT; + + using TableTupleType = table_tuple_t; +}; + +template +dynamic::SelectFrom to_select_from(const auto& _fields, + const auto& _table_or_query, + const auto& _joins, const auto& _where, + const auto& _limit) { + using TableTupleType = typename SelectFromT::TableTupleType; + using AliasType = typename SelectFromT::AliasType; + using FieldsType = typename SelectFromT::FieldsType; + using GroupByType = typename SelectFromT::GroupByType; + using OrderByType = typename SelectFromT::OrderByType; + static_assert(check_aggregations, GroupByType>(), diff --git a/include/sqlgen/transpilation/to_sql.hpp b/include/sqlgen/transpilation/to_sql.hpp index 05f79fc0..9b11cc4f 100644 --- a/include/sqlgen/transpilation/to_sql.hpp +++ b/include/sqlgen/transpilation/to_sql.hpp @@ -12,6 +12,7 @@ #include "../insert.hpp" #include "../read.hpp" #include "../select_from.hpp" +#include "../unite.hpp" #include "../update.hpp" #include "columns_t.hpp" #include "read_to_select_from.hpp" @@ -22,6 +23,7 @@ #include "to_drop.hpp" #include "to_insert_or_write.hpp" #include "to_select_from.hpp" +#include "to_union.hpp" #include "to_update.hpp" #include "value_t.hpp" @@ -96,18 +98,11 @@ struct ToSQL> { } }; -template -struct ToSQL< - SelectFrom> { +template +struct ToSQL> { dynamic::Statement operator()(const auto& _select_from) const { - using TableTupleType = - table_tuple_t; - return to_select_from( + using SelectFromTypes = typename SelectFrom::SelectFromTypes; + return to_select_from( _select_from.fields_, _select_from.from_, _select_from.joins_, _select_from.where_, _select_from.limit_); } @@ -120,6 +115,13 @@ struct ToSQL> { } }; +template +struct ToSQL> { + dynamic::Statement operator()(const auto& _union) const { + return to_union(_union.selects_, _union.all_); + } +}; + template dynamic::Statement to_sql(const T& _t) { return ToSQL>{}(_t); diff --git a/include/sqlgen/transpilation/to_union.hpp b/include/sqlgen/transpilation/to_union.hpp new file mode 100644 index 00000000..7adff157 --- /dev/null +++ b/include/sqlgen/transpilation/to_union.hpp @@ -0,0 +1,40 @@ +#ifndef SQLGEN_TRANSPILATION_TO_UNION_HPP_ +#define SQLGEN_TRANSPILATION_TO_UNION_HPP_ + +#include +#include +#include + +#include "../Ref.hpp" +#include "../dynamic/SelectFrom.hpp" +#include "../dynamic/Union.hpp" +#include "table_tuple_t.hpp" +#include "to_select_from.hpp" +#include "value_t.hpp" + +namespace sqlgen::transpilation { + +template +dynamic::Union to_union(const rfl::Tuple& _stmts, + const bool _all) noexcept { + using ValueType = value_t; + using NamedTupleType = rfl::named_tuple_t; + + const auto columns = NamedTupleType::Names::names(); + + const auto selects = rfl::apply( + [](const StmtTs... _stmt) { + auto vec = std::vector( + {to_select_from( + _stmt.fields_, _stmt.from_, _stmt.joins_, _stmt.where_, + _stmt.limit_)...}); + return Ref>::make(std::move(vec)); + }, + _stmts); + + return dynamic::Union{.columns = columns, .selects = selects, .all = _all}; +} + +} // namespace sqlgen::transpilation + +#endif diff --git a/include/sqlgen/unite.hpp b/include/sqlgen/unite.hpp new file mode 100644 index 00000000..6cde8976 --- /dev/null +++ b/include/sqlgen/unite.hpp @@ -0,0 +1,185 @@ +#ifndef SQLGEN_UNITE_HPP_ +#define SQLGEN_UNITE_HPP_ + +#include +#include + +#include "Literal.hpp" +#include "Range.hpp" +#include "Ref.hpp" +#include "Result.hpp" +#include "dynamic/Union.hpp" +#include "internal/is_range.hpp" +#include "internal/iterator_t.hpp" +#include "is_connection.hpp" +#include "transpilation/Union.hpp" +#include "transpilation/fields_to_named_tuple_t.hpp" +#include "transpilation/get_table_t.hpp" +#include "transpilation/to_union.hpp" +#include "transpilation/value_t.hpp" +#include "transpilation/wrap_in_optional_t.hpp" + +namespace sqlgen { + +template + requires is_connection +auto unite_impl(const Ref& _conn, + const rfl::Tuple& _stmts, const bool _all) { + if constexpr (internal::is_range_v) { + const auto query = transpilation::to_union(_stmts, _all); + return _conn->template read(query); + + } else { + const auto to_container = [](auto range) -> Result { + using ValueType = transpilation::value_t; + ContainerType container; + for (auto& res : range) { + if (res) { + container.emplace_back( + rfl::from_named_tuple(std::move(*res))); + } else { + return error("One of the results in the union was an error."); + } + } + return container; + }; + + using IteratorType = + internal::iterator_t>, + decltype(_conn)>; + + using RangeType = Range; + + return unite_impl(_conn, _stmts, _all).and_then(to_container); + } +} + +template +struct Union { + template + requires is_connection + auto operator()(const Ref& _conn) const { + using ContainerType = std::conditional_t< + std::is_same_v, Nothing>, + Range>, + Connection>>, + _ContainerType>; + + return unite_impl(_conn, selects_, all_); + } + + template + requires is_connection + auto operator()(const Result>& _res) const { + return _res.and_then([&](const auto& _conn) { return (*this)(_conn); }); + } + + rfl::Tuple selects_; + bool all_ = false; +}; + +namespace transpilation { + +template +struct ExtractTable, false> { + using Type = std::conditional_t< + std::is_same_v, Nothing>, + fields_to_named_tuple_t>, value_t>; +}; + +template +struct ExtractTable, true> { + using Type = wrap_in_optional_t, false>::Type>; +}; + +template +struct ToTableOrQuery> { + dynamic::SelectFrom::TableOrQueryType operator()(const auto& _stmt) { + const auto query = to_union(_stmt.selects_, _stmt.all_); + return Ref::make(query); + } +}; + +template +struct FieldsToNamedTupleType, + FieldTs...> { + using Type = fields_to_named_tuple_t, FieldTs...>; +}; + +template +struct FieldsToNamedTupleType, + rfl::Tuple> { + using Type = fields_to_named_tuple_t, FieldTs...>; +}; + +template +struct GetTableType, + sqlgen::Union> { + using TableType = get_table_t< + Literal<_alias>, + rfl::Tuple, false>, + Literal<_alias>>>>; +}; + +template +struct GetTableType, sqlgen::Union> { + using TableType = get_table_t< + Literal<"">, + extract_table_t, false>>; +}; + +template +struct GetTableType, + sqlgen::Union> { + using TableType = get_table_t< + std::integral_constant, + extract_table_t, false>>; +}; + +template +struct TableTupleType, AliasType, + Nothing> { + using Type = rfl::Tuple, false>, + AliasType>>; +}; + +template +struct TableTupleType, Literal<"">, + Nothing> { + using Type = + extract_table_t, false>; +}; + +} // namespace transpilation + +template +auto unite(const SelectTs&... _stmts) { + return Union{ + .selects_ = rfl::Tuple(_stmts...), .all_ = false}; +} + +template +auto unite(const SelectTs&... _stmts) { + return unite(_stmts...); +} + +template +auto unite_all(const SelectTs&... _stmts) { + return Union{ + .selects_ = rfl::Tuple(_stmts...), .all_ = true}; +} + +template +auto unite_all(const SelectTs&... _stmts) { + return unite_all(_stmts...); +} + +} // namespace sqlgen + +#endif diff --git a/src/sqlgen/duckdb/to_sql.cpp b/src/sqlgen/duckdb/to_sql.cpp index 498e913b..bbd92ace 100644 --- a/src/sqlgen/duckdb/to_sql.cpp +++ b/src/sqlgen/duckdb/to_sql.cpp @@ -72,6 +72,8 @@ std::string table_or_query_to_sql( std::string type_to_sql(const dynamic::Type& _type) noexcept; +std::string union_to_sql(const dynamic::Union& _stmt) noexcept; + std::string update_to_sql(const dynamic::Update& _stmt) noexcept; std::string write_to_sql(const dynamic::Write& _stmt) noexcept; @@ -798,6 +800,10 @@ std::string table_or_query_to_sql( return wrap_in_quotes(*_t.schema) + "." + wrap_in_quotes(_t.name); } return wrap_in_quotes(_t.name); + + } else if constexpr (std::is_same_v>) { + return "(" + union_to_sql(*_t) + ")"; + } else { return "(" + select_from_to_sql(*_t) + ")"; } @@ -829,6 +835,9 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept { } else if constexpr (std::is_same_v) { return select_from_to_sql(_s); + } else if constexpr (std::is_same_v) { + return union_to_sql(_s); + } else if constexpr (std::is_same_v) { return update_to_sql(_s); @@ -909,6 +918,24 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept { }); } +std::string union_to_sql(const dynamic::Union& _stmt) noexcept { + using namespace std::ranges::views; + + const auto columns = internal::strings::join( + ", ", + internal::collect::vector(_stmt.columns | transform(wrap_in_quotes))); + + const auto to_str = [&](const auto& _select) { + return "SELECT " + columns + " FROM (" + select_from_to_sql(_select) + ")"; + }; + + const auto separator = + _stmt.all ? std::string(" UNION ALL ") : std::string(" UNION "); + + return internal::strings::join( + separator, internal::collect::vector(*_stmt.selects | transform(to_str))); +} + std::string update_to_sql(const dynamic::Update& _stmt) noexcept { using namespace std::ranges::views; diff --git a/src/sqlgen/mysql/Connection.cpp b/src/sqlgen/mysql/Connection.cpp index 2e1d407d..1eefa8c5 100644 --- a/src/sqlgen/mysql/Connection.cpp +++ b/src/sqlgen/mysql/Connection.cpp @@ -137,8 +137,10 @@ Result Connection::prepare_statement( return stmt_ptr; } -Result> Connection::read_impl(const dynamic::SelectFrom& _query) { - const auto sql = mysql::to_sql_impl(_query); +Result> Connection::read_impl( + const rfl::Variant& _query) { + const auto sql = + _query.visit([](const auto& _q) { return mysql::to_sql_impl(_q); }); const auto err = mysql_real_query(conn_.get(), sql.c_str(), static_cast(sql.size())); if (err) { diff --git a/src/sqlgen/mysql/to_sql.cpp b/src/sqlgen/mysql/to_sql.cpp index 35ef6592..9afb4e08 100644 --- a/src/sqlgen/mysql/to_sql.cpp +++ b/src/sqlgen/mysql/to_sql.cpp @@ -72,6 +72,8 @@ std::string table_or_query_to_sql( std::string type_to_sql(const dynamic::Type& _type) noexcept; +std::string union_to_sql(const dynamic::Union& _stmt) noexcept; + std::string update_to_sql(const dynamic::Update& _stmt) noexcept; // ---------------------------------------------------------------------------- @@ -837,6 +839,10 @@ std::string table_or_query_to_sql( return wrap_in_quotes(*_t.schema) + "." + wrap_in_quotes(_t.name); } return wrap_in_quotes(_t.name); + + } else if constexpr (std::is_same_v>) { + return "(" + union_to_sql(*_t) + ")"; + } else { return "(" + select_from_to_sql(*_t) + ")"; } @@ -872,12 +878,36 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept { } else if constexpr (std::is_same_v) { return update_to_sql(_s); + } else if constexpr (std::is_same_v) { + return union_to_sql(_s); + } else { static_assert(rfl::always_false_v, "Unsupported type."); } }); } +std::string union_to_sql(const dynamic::Union& _stmt) noexcept { + using namespace std::ranges::views; + + const auto columns = internal::strings::join( + ", ", + internal::collect::vector(_stmt.columns | transform([](const auto& _col) { + return "t." + wrap_in_quotes(_col); + }))); + + const auto to_str = [&](const auto& _select) { + return "SELECT " + columns + " FROM (" + select_from_to_sql(_select) + + ") t"; + }; + + const auto separator = + _stmt.all ? std::string(" UNION ALL ") : std::string(" UNION "); + + return internal::strings::join( + separator, internal::collect::vector(*_stmt.selects | transform(to_str))); +} + std::string type_to_sql(const dynamic::Type& _type) noexcept { return _type.visit([](const auto _t) -> std::string { using T = std::remove_cvref_t; diff --git a/src/sqlgen/postgres/Connection.cpp b/src/sqlgen/postgres/Connection.cpp index 0815559f..350e6adc 100644 --- a/src/sqlgen/postgres/Connection.cpp +++ b/src/sqlgen/postgres/Connection.cpp @@ -110,8 +110,9 @@ rfl::Result> Connection::make( .transform([](auto&& _conn) { return Ref::make(_conn); }); } -Result> Connection::read_impl(const dynamic::SelectFrom& _query) { - const auto sql = postgres::to_sql_impl(_query); +Result> Connection::read_impl( + const rfl::Variant& _query) { + const auto sql = _query.visit([](const auto& _q) { return to_sql_impl(_q); }); return Ref::make(sql, conn_); } diff --git a/src/sqlgen/postgres/to_sql.cpp b/src/sqlgen/postgres/to_sql.cpp index ab491aca..8eeac730 100644 --- a/src/sqlgen/postgres/to_sql.cpp +++ b/src/sqlgen/postgres/to_sql.cpp @@ -61,6 +61,8 @@ std::string table_or_query_to_sql( std::string type_to_sql(const dynamic::Type& _type) noexcept; +std::string union_to_sql(const dynamic::Union& _stmt) noexcept; + std::string update_to_sql(const dynamic::Update& _stmt) noexcept; std::string write_to_sql(const dynamic::Write& _stmt) noexcept; @@ -762,6 +764,10 @@ std::string table_or_query_to_sql( return wrap_in_quotes(*_t.schema) + "." + wrap_in_quotes(_t.name); } return wrap_in_quotes(_t.name); + + } else if constexpr (std::is_same_v>) { + return "(" + union_to_sql(*_t) + ")"; + } else { return "(" + select_from_to_sql(*_t) + ")"; } @@ -799,12 +805,33 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept { } else if constexpr (std::is_same_v) { return write_to_sql(_s); + } else if constexpr (std::is_same_v) { + return union_to_sql(_s); + } else { static_assert(rfl::always_false_v, "Unsupported type."); } }); } +std::string union_to_sql(const dynamic::Union& _stmt) noexcept { + using namespace std::ranges::views; + + const auto columns = internal::strings::join( + ", ", + internal::collect::vector(_stmt.columns | transform(wrap_in_quotes))); + + const auto to_str = [&](const auto& _select) { + return "SELECT " + columns + " FROM (" + select_from_to_sql(_select) + ")"; + }; + + const auto separator = + _stmt.all ? std::string(" UNION ALL ") : std::string(" UNION "); + + return internal::strings::join( + separator, internal::collect::vector(*_stmt.selects | transform(to_str))); +} + std::string type_to_sql(const dynamic::Type& _type) noexcept { return _type.visit([](const auto _t) -> std::string { using T = std::remove_cvref_t; diff --git a/src/sqlgen/sqlite/Connection.cpp b/src/sqlgen/sqlite/Connection.cpp index 0aa44d98..3d60ccbc 100644 --- a/src/sqlgen/sqlite/Connection.cpp +++ b/src/sqlgen/sqlite/Connection.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "sqlgen/internal/collect/vector.hpp" @@ -101,8 +102,9 @@ typename Connection::ConnPtr Connection::make_conn(const std::string& _fname) { return ConnPtr::make(std::shared_ptr(conn, &sqlite3_close)).value(); } -Result> Connection::read_impl(const dynamic::SelectFrom& _query) { - const auto sql = to_sql_impl(_query); +Result> Connection::read_impl( + const rfl::Variant& _query) { + const auto sql = _query.visit([](const auto& _q) { return to_sql_impl(_q); }); sqlite3_stmt* p_stmt = nullptr; diff --git a/src/sqlgen/sqlite/to_sql.cpp b/src/sqlgen/sqlite/to_sql.cpp index d38e6873..11551c72 100644 --- a/src/sqlgen/sqlite/to_sql.cpp +++ b/src/sqlgen/sqlite/to_sql.cpp @@ -53,6 +53,8 @@ std::string table_or_query_to_sql( std::string type_to_sql(const dynamic::Type& _type) noexcept; +std::string union_to_sql(const dynamic::Union& _stmt) noexcept; + std::string update_to_sql(const dynamic::Update& _stmt) noexcept; // ---------------------------------------------------------------------------- @@ -720,6 +722,10 @@ std::string table_or_query_to_sql( return wrap_in_quotes(*_t.schema) + "." + wrap_in_quotes(_t.name); } return wrap_in_quotes(_t.name); + + } else if constexpr (std::is_same_v>) { + return "(" + union_to_sql(*_t) + ")"; + } else { return "(" + select_from_to_sql(*_t) + ")"; } @@ -756,12 +762,33 @@ std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept { } else if constexpr (std::is_same_v) { return insert_or_write_to_sql(_s); + } else if constexpr (std::is_same_v) { + return union_to_sql(_s); + } else { static_assert(rfl::always_false_v, "Unsupported type."); } }); } +std::string union_to_sql(const dynamic::Union& _stmt) noexcept { + using namespace std::ranges::views; + + const auto columns = internal::strings::join( + ", ", + internal::collect::vector(_stmt.columns | transform(wrap_in_quotes))); + + const auto to_str = [&](const auto& _select) { + return "SELECT " + columns + " FROM (" + select_from_to_sql(_select) + ")"; + }; + + const auto separator = + _stmt.all ? std::string(" UNION ALL ") : std::string(" UNION "); + + return internal::strings::join( + separator, internal::collect::vector(*_stmt.selects | transform(to_str))); +} + std::string type_to_sql(const dynamic::Type& _type) noexcept { return _type.visit([](const auto _t) -> std::string { using T = std::remove_cvref_t; diff --git a/tests/duckdb/test_union.cpp b/tests/duckdb/test_union.cpp new file mode 100644 index 00000000..cc486ff4 --- /dev/null +++ b/tests/duckdb/test_union.cpp @@ -0,0 +1,60 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/duckdb/to_sql.hpp" + +namespace test_union { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(duckdb, test_union) { + using namespace sqlgen::literals; + + const auto conn = sqlgen::duckdb::connect(); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto result = sqlgen::unite>(s1, s2, s3)(conn); + + const auto users = result.value(); + + const auto query = + sqlgen::duckdb::to_sql(sqlgen::unite>(s1, s2, s3)); + + EXPECT_EQ( + query, + R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3"))"); + + EXPECT_EQ(users.size(), 3); +} + +} // namespace test_union diff --git a/tests/duckdb/test_union_all.cpp b/tests/duckdb/test_union_all.cpp new file mode 100644 index 00000000..0607b58c --- /dev/null +++ b/tests/duckdb/test_union_all.cpp @@ -0,0 +1,60 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/duckdb/to_sql.hpp" + +namespace test_union_all { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(duckdb, test_union_all) { + using namespace sqlgen::literals; + + const auto conn = sqlgen::duckdb::connect(); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 30, .name = "John"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto result = sqlgen::unite_all>(s1, s2, s3)(conn); + + const auto users = result.value(); + + const auto query = + sqlgen::duckdb::to_sql(sqlgen::unite_all>(s1, s2, s3)); + + EXPECT_EQ( + query, + R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION ALL SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION ALL SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3"))"); + + EXPECT_EQ(users.size(), 3); +} + +} // namespace test_union_all diff --git a/tests/duckdb/test_union_in_join.cpp b/tests/duckdb/test_union_in_join.cpp new file mode 100644 index 00000000..2c1c3f8e --- /dev/null +++ b/tests/duckdb/test_union_in_join.cpp @@ -0,0 +1,76 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/duckdb/to_sql.hpp" + +namespace test_union_in_join { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +struct Login { + int id; + std::string username; +}; + +TEST(duckdb, test_union_in_join) { + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto conn = sqlgen::duckdb::connect(); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto login = Login{.id = 1, .username = "John"}; + sqlgen::write(conn, login); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = select_from("id"_t1, "username"_t1) | + inner_join<"t2">(united, "username"_t1 == "name"_t2) | + where("id"_t1 == 1) | to>; + + const auto query = sqlgen::duckdb::to_sql(sel); + + const auto result = sel(conn); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t1."id", t1."username" FROM "Login" t1 INNER JOIN (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")) t2 ON t1."username" = t2."name" WHERE t1."id" = 1)"); + + EXPECT_EQ(users.size(), 1); + EXPECT_EQ(users.at(0).id, 1); + EXPECT_EQ(users.at(0).username, "John"); +} + +} // namespace test_union_in_join diff --git a/tests/duckdb/test_union_in_join2.cpp b/tests/duckdb/test_union_in_join2.cpp new file mode 100644 index 00000000..d040085e --- /dev/null +++ b/tests/duckdb/test_union_in_join2.cpp @@ -0,0 +1,76 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/duckdb/to_sql.hpp" + +namespace test_union_in_join2 { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +struct Login { + int id; + std::string username; +}; + +TEST(duckdb, test_union_in_join2) { + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto conn = sqlgen::duckdb::connect(); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto login = Login{.id = 1, .username = "John"}; + sqlgen::write(conn, login); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = select_from<"t1">(united, "id"_t2, "username"_t2) | + inner_join("username"_t2 == "name"_t1) | + where("id"_t2 == 1) | to>; + + const auto query = sqlgen::duckdb::to_sql(sel); + + const auto result = sel(conn); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t2."id", t2."username" FROM (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")) t1 INNER JOIN "Login" t2 ON t2."username" = t1."name" WHERE t2."id" = 1)"); + + EXPECT_EQ(users.size(), 1); + EXPECT_EQ(users.at(0).id, 1); + EXPECT_EQ(users.at(0).username, "John"); +} + +} // namespace test_union_in_join2 diff --git a/tests/duckdb/test_union_in_select.cpp b/tests/duckdb/test_union_in_select.cpp new file mode 100644 index 00000000..706ee3ff --- /dev/null +++ b/tests/duckdb/test_union_in_select.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/duckdb/to_sql.hpp" + +namespace test_union_in_select { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(duckdb, test_union_in_select) { + using namespace sqlgen::literals; + + const auto conn = sqlgen::duckdb::connect(); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = sqlgen::select_from(united, "name"_c, "age"_c) | + sqlgen::to>; + + const auto result = sel(conn); + + const auto users = result.value(); + + const auto query = sqlgen::duckdb::to_sql(sel); + + EXPECT_EQ( + query, + R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")))"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "Jane"); + EXPECT_EQ(users.at(0).age, 25); + EXPECT_EQ(users.at(1).name, "Joe"); + EXPECT_EQ(users.at(1).age, 40); + EXPECT_EQ(users.at(2).name, "John"); + EXPECT_EQ(users.at(2).age, 30); +} + +} // namespace test_union_in_select diff --git a/tests/mysql/test_union.cpp b/tests/mysql/test_union.cpp new file mode 100644 index 00000000..b51d66e7 --- /dev/null +++ b/tests/mysql/test_union.cpp @@ -0,0 +1,76 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +namespace test_union { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(mysql, test_union) { + using namespace sqlgen::literals; + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + const auto conn = sqlgen::mysql::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto result = sqlgen::unite>(s1, s2, s3)(conn); + + const auto query = + sqlgen::mysql::to_sql(sqlgen::unite>(s1, s2, s3)); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User1`) t UNION SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User2`) t UNION SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User3`) t)"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "John"); + EXPECT_EQ(users.at(0).age, 30); + EXPECT_EQ(users.at(1).name, "Jane"); + EXPECT_EQ(users.at(1).age, 25); + EXPECT_EQ(users.at(2).name, "Joe"); + EXPECT_EQ(users.at(2).age, 40); +} + +} // namespace test_union + +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/mysql/test_union_all.cpp b/tests/mysql/test_union_all.cpp new file mode 100644 index 00000000..0c656626 --- /dev/null +++ b/tests/mysql/test_union_all.cpp @@ -0,0 +1,78 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/mysql/to_sql.hpp" + +namespace test_union_all { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(mysql, test_union_all) { + using namespace sqlgen::literals; + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + const auto conn = sqlgen::mysql::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 30, .name = "John"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto result = sqlgen::unite_all>(s1, s2, s3)(conn); + + const auto query = + sqlgen::mysql::to_sql(sqlgen::unite_all>(s1, s2, s3)); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User1`) t UNION ALL SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User2`) t UNION ALL SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User3`) t)"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "John"); + EXPECT_EQ(users.at(0).age, 30); + EXPECT_EQ(users.at(1).name, "Jane"); + EXPECT_EQ(users.at(1).age, 25); + EXPECT_EQ(users.at(2).name, "John"); + EXPECT_EQ(users.at(2).age, 30); +} + +} // namespace test_union_all + +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/mysql/test_union_in_join.cpp b/tests/mysql/test_union_in_join.cpp new file mode 100644 index 00000000..a644275b --- /dev/null +++ b/tests/mysql/test_union_in_join.cpp @@ -0,0 +1,89 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/mysql/to_sql.hpp" + +namespace test_union_in_join { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +struct Login { + int id; + std::string username; +}; + +TEST(mysql, test_union_in_join) { + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + const auto conn = sqlgen::mysql::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto login = Login{.id = 1, .username = "John"}; + sqlgen::write(conn, login); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = select_from("id"_t1, "username"_t1) | + inner_join<"t2">(united, "username"_t1 == "name"_t2) | + where("id"_t1 == 1) | to>; + + const auto query = sqlgen::mysql::to_sql(sel); + + const auto result = sel(conn); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t1.`id`, t1.`username` FROM `Login` t1 INNER JOIN (SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User1`) t UNION SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User2`) t UNION SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User3`) t) t2 ON t1.`username` = t2.`name` WHERE t1.`id` = 1)"); + + EXPECT_EQ(users.size(), 1); + EXPECT_EQ(users.at(0).id, 1); + EXPECT_EQ(users.at(0).username, "John"); +} + +} // namespace test_union_in_join + +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/mysql/test_union_in_join2.cpp b/tests/mysql/test_union_in_join2.cpp new file mode 100644 index 00000000..e52599a7 --- /dev/null +++ b/tests/mysql/test_union_in_join2.cpp @@ -0,0 +1,89 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/mysql/to_sql.hpp" + +namespace test_union_in_join2 { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +struct Login { + int id; + std::string username; +}; + +TEST(mysql, test_union_in_join2) { + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + const auto conn = sqlgen::mysql::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto login = Login{.id = 1, .username = "John"}; + sqlgen::write(conn, login); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = select_from<"t1">(united, "id"_t2, "username"_t2) | + inner_join("username"_t2 == "name"_t1) | + where("id"_t2 == 1) | to>; + + const auto query = sqlgen::mysql::to_sql(sel); + + const auto result = sel(conn); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t2.`id`, t2.`username` FROM (SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User1`) t UNION SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User2`) t UNION SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User3`) t) t1 INNER JOIN `Login` t2 ON t2.`username` = t1.`name` WHERE t2.`id` = 1)"); + + EXPECT_EQ(users.size(), 1); + EXPECT_EQ(users.at(0).id, 1); + EXPECT_EQ(users.at(0).username, "John"); +} + +} // namespace test_union_in_join2 + +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/mysql/test_union_in_select.cpp b/tests/mysql/test_union_in_select.cpp new file mode 100644 index 00000000..e3ce3891 --- /dev/null +++ b/tests/mysql/test_union_in_select.cpp @@ -0,0 +1,81 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/mysql/to_sql.hpp" + +namespace test_union_in_select { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(mysql, test_union_in_select) { + using namespace sqlgen::literals; + + const auto credentials = sqlgen::mysql::Credentials{.host = "localhost", + .user = "sqlgen", + .password = "password", + .dbname = "mysql"}; + + const auto conn = sqlgen::mysql::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = sqlgen::select_from<"t1">(united, "name"_t1, "age"_t1) | + sqlgen::to>; + + const auto query = sqlgen::mysql::to_sql(sel); + + const auto result = sel(conn); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t1.`name`, t1.`age` FROM (SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User1`) t UNION SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User2`) t UNION SELECT t.`name`, t.`age` FROM (SELECT `name`, `age` FROM `User3`) t) t1)"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "John"); + EXPECT_EQ(users.at(0).age, 30); + EXPECT_EQ(users.at(1).name, "Jane"); + EXPECT_EQ(users.at(1).age, 25); + EXPECT_EQ(users.at(2).name, "Joe"); + EXPECT_EQ(users.at(2).age, 40); +} + +} // namespace test_union_in_select +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/postgres/test_union.cpp b/tests/postgres/test_union.cpp new file mode 100644 index 00000000..8ce15d49 --- /dev/null +++ b/tests/postgres/test_union.cpp @@ -0,0 +1,78 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/postgres/to_sql.hpp" + +namespace test_union { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(postgres, test_union) { + using namespace sqlgen::literals; + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + const auto conn = sqlgen::postgres::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto result = sqlgen::unite>(s1, s2, s3)(conn); + + const auto users = result.value(); + + const auto query = + sqlgen::postgres::to_sql(sqlgen::unite>(s1, s2, s3)); + + EXPECT_EQ( + query, + R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3"))"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "John"); + EXPECT_EQ(users.at(0).age, 30); + EXPECT_EQ(users.at(1).name, "Jane"); + EXPECT_EQ(users.at(1).age, 25); + EXPECT_EQ(users.at(2).name, "Joe"); + EXPECT_EQ(users.at(2).age, 40); +} + +} // namespace test_union + +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/postgres/test_union_all.cpp b/tests/postgres/test_union_all.cpp new file mode 100644 index 00000000..6190a7e1 --- /dev/null +++ b/tests/postgres/test_union_all.cpp @@ -0,0 +1,78 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/postgres/to_sql.hpp" + +namespace test_union_all { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(postgres, test_union_all) { + using namespace sqlgen::literals; + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + const auto conn = sqlgen::postgres::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 30, .name = "John"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto result = sqlgen::unite_all>(s1, s2, s3)(conn); + + const auto users = result.value(); + + const auto query = sqlgen::postgres::to_sql( + sqlgen::unite_all>(s1, s2, s3)); + + EXPECT_EQ( + query, + R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION ALL SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION ALL SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3"))"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "John"); + EXPECT_EQ(users.at(0).age, 30); + EXPECT_EQ(users.at(1).name, "Jane"); + EXPECT_EQ(users.at(1).age, 25); + EXPECT_EQ(users.at(2).name, "John"); + EXPECT_EQ(users.at(2).age, 30); +} + +} // namespace test_union_all + +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/postgres/test_union_in_join.cpp b/tests/postgres/test_union_in_join.cpp new file mode 100644 index 00000000..910eb381 --- /dev/null +++ b/tests/postgres/test_union_in_join.cpp @@ -0,0 +1,89 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/postgres/to_sql.hpp" + +namespace test_union_in_join { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +struct Login { + int id; + std::string username; +}; + +TEST(postgres, test_union_in_join) { + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + const auto conn = sqlgen::postgres::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto login = Login{.id = 1, .username = "John"}; + sqlgen::write(conn, login); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = select_from("id"_t1, "username"_t1) | + inner_join<"t2">(united, "username"_t1 == "name"_t2) | + where("id"_t1 == 1) | to>; + + const auto query = sqlgen::postgres::to_sql(sel); + + const auto result = sel(conn); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t1."id", t1."username" FROM "Login" t1 INNER JOIN (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")) t2 ON t1."username" = t2."name" WHERE t1."id" = 1)"); + + EXPECT_EQ(users.size(), 1); + EXPECT_EQ(users.at(0).id, 1); + EXPECT_EQ(users.at(0).username, "John"); +} + +} // namespace test_union_in_join + +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/postgres/test_union_in_join2.cpp b/tests/postgres/test_union_in_join2.cpp new file mode 100644 index 00000000..60ce6dfd --- /dev/null +++ b/tests/postgres/test_union_in_join2.cpp @@ -0,0 +1,89 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/postgres/to_sql.hpp" + +namespace test_union_in_join2 { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +struct Login { + int id; + std::string username; +}; + +TEST(postgres, test_union_in_join2) { + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + const auto conn = sqlgen::postgres::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto login = Login{.id = 1, .username = "John"}; + sqlgen::write(conn, login); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = select_from<"t1">(united, "id"_t2, "username"_t2) | + inner_join("username"_t2 == "name"_t1) | + where("id"_t2 == 1) | to>; + + const auto query = sqlgen::postgres::to_sql(sel); + + const auto result = sel(conn); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t2."id", t2."username" FROM (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")) t1 INNER JOIN "Login" t2 ON t2."username" = t1."name" WHERE t2."id" = 1)"); + + EXPECT_EQ(users.size(), 1); + EXPECT_EQ(users.at(0).id, 1); + EXPECT_EQ(users.at(0).username, "John"); +} + +} // namespace test_union_in_join2 + +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/postgres/test_union_in_select.cpp b/tests/postgres/test_union_in_select.cpp new file mode 100644 index 00000000..51b219bc --- /dev/null +++ b/tests/postgres/test_union_in_select.cpp @@ -0,0 +1,81 @@ +#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY + +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/postgres/to_sql.hpp" + +namespace test_union_in_select { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(postgres, test_union_in_select) { + using namespace sqlgen::literals; + + const auto credentials = sqlgen::postgres::Credentials{.user = "postgres", + .password = "password", + .host = "localhost", + .dbname = "postgres"}; + + const auto conn = sqlgen::postgres::connect(credentials) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists) + .and_then(sqlgen::drop | sqlgen::if_exists); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = sqlgen::select_from(united, "name"_c, "age"_c) | + sqlgen::to>; + + const auto result = sel(conn); + + const auto users = result.value(); + + const auto query = sqlgen::postgres::to_sql(sel); + + EXPECT_EQ( + query, + R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")))"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "John"); + EXPECT_EQ(users.at(0).age, 30); + EXPECT_EQ(users.at(1).name, "Jane"); + EXPECT_EQ(users.at(1).age, 25); + EXPECT_EQ(users.at(2).name, "Joe"); + EXPECT_EQ(users.at(2).age, 40); +} + +} // namespace test_union_in_select +#endif // SQLGEN_BUILD_DRY_TESTS_ONLY diff --git a/tests/sqlite/test_union.cpp b/tests/sqlite/test_union.cpp new file mode 100644 index 00000000..03a3a2f4 --- /dev/null +++ b/tests/sqlite/test_union.cpp @@ -0,0 +1,66 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/sqlite/to_sql.hpp" + +namespace test_union { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(sqlite, test_union) { + using namespace sqlgen::literals; + + const auto conn = sqlgen::sqlite::connect(); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto result = sqlgen::unite>(s1, s2, s3)(conn); + + const auto users = result.value(); + + const auto query = + sqlgen::sqlite::to_sql(sqlgen::unite>(s1, s2, s3)); + + EXPECT_EQ( + query, + R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3"))"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "Jane"); + EXPECT_EQ(users.at(0).age, 25); + EXPECT_EQ(users.at(1).name, "Joe"); + EXPECT_EQ(users.at(1).age, 40); + EXPECT_EQ(users.at(2).name, "John"); + EXPECT_EQ(users.at(2).age, 30); +} + +} // namespace test_union diff --git a/tests/sqlite/test_union_all.cpp b/tests/sqlite/test_union_all.cpp new file mode 100644 index 00000000..93fde6fc --- /dev/null +++ b/tests/sqlite/test_union_all.cpp @@ -0,0 +1,66 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/sqlite/to_sql.hpp" + +namespace test_union_all { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(sqlite, test_union_all) { + using namespace sqlgen::literals; + + const auto conn = sqlgen::sqlite::connect(); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 30, .name = "John"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto result = sqlgen::unite_all>(s1, s2, s3)(conn); + + const auto users = result.value(); + + const auto query = + sqlgen::sqlite::to_sql(sqlgen::unite_all>(s1, s2, s3)); + + EXPECT_EQ( + query, + R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION ALL SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION ALL SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3"))"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "John"); + EXPECT_EQ(users.at(0).age, 30); + EXPECT_EQ(users.at(1).name, "Jane"); + EXPECT_EQ(users.at(1).age, 25); + EXPECT_EQ(users.at(2).name, "John"); + EXPECT_EQ(users.at(2).age, 30); +} + +} // namespace test_union_all diff --git a/tests/sqlite/test_union_in_join.cpp b/tests/sqlite/test_union_in_join.cpp new file mode 100644 index 00000000..b4a9e778 --- /dev/null +++ b/tests/sqlite/test_union_in_join.cpp @@ -0,0 +1,76 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/sqlite/to_sql.hpp" + +namespace test_union_in_join { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +struct Login { + int id; + std::string username; +}; + +TEST(sqlite, test_union_in_join) { + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto conn = sqlgen::sqlite::connect(); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto login = Login{.id = 1, .username = "John"}; + sqlgen::write(conn, login); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = select_from("id"_t1, "username"_t1) | + inner_join<"t2">(united, "username"_t1 == "name"_t2) | + where("id"_t1 == 1) | to>; + + const auto query = sqlgen::sqlite::to_sql(sel); + + const auto result = sel(conn); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t1."id", t1."username" FROM "Login" t1 INNER JOIN (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")) t2 ON t1."username" = t2."name" WHERE t1."id" = 1)"); + + EXPECT_EQ(users.size(), 1); + EXPECT_EQ(users.at(0).id, 1); + EXPECT_EQ(users.at(0).username, "John"); +} + +} // namespace test_union_in_join diff --git a/tests/sqlite/test_union_in_join2.cpp b/tests/sqlite/test_union_in_join2.cpp new file mode 100644 index 00000000..d8b07ed0 --- /dev/null +++ b/tests/sqlite/test_union_in_join2.cpp @@ -0,0 +1,76 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/sqlite/to_sql.hpp" + +namespace test_union_in_join2 { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +struct Login { + int id; + std::string username; +}; + +TEST(sqlite, test_union_in_join2) { + using namespace sqlgen; + using namespace sqlgen::literals; + + const auto conn = sqlgen::sqlite::connect(); + + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto login = Login{.id = 1, .username = "John"}; + sqlgen::write(conn, login); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = select_from<"t1">(united, "id"_t2, "username"_t2) | + inner_join("username"_t2 == "name"_t1) | + where("id"_t2 == 1) | to>; + + const auto query = sqlgen::sqlite::to_sql(sel); + + const auto result = sel(conn); + + const auto users = result.value(); + + EXPECT_EQ( + query, + R"(SELECT t2."id", t2."username" FROM (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")) t1 INNER JOIN "Login" t2 ON t2."username" = t1."name" WHERE t2."id" = 1)"); + + EXPECT_EQ(users.size(), 1); + EXPECT_EQ(users.at(0).id, 1); + EXPECT_EQ(users.at(0).username, "John"); +} + +} // namespace test_union_in_join2 diff --git a/tests/sqlite/test_union_in_select.cpp b/tests/sqlite/test_union_in_select.cpp new file mode 100644 index 00000000..2b08b4b6 --- /dev/null +++ b/tests/sqlite/test_union_in_select.cpp @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include +#include + +#include "sqlgen/sqlite/to_sql.hpp" + +namespace test_union_in_select { + +struct User1 { + std::string name; + int age; +}; + +struct User2 { + std::string name; + int age; +}; + +struct User3 { + int age; + std::string name; +}; + +TEST(sqlite, test_union_in_select) { + using namespace sqlgen::literals; + + // Connect to SQLite database + const auto conn = sqlgen::sqlite::connect(); + + // Create and insert a user + const auto user1 = User1{.name = "John", .age = 30}; + sqlgen::write(conn, user1); + + const auto user2 = User2{.name = "Jane", .age = 25}; + sqlgen::write(conn, user2); + + const auto user3 = User3{.age = 40, .name = "Joe"}; + sqlgen::write(conn, user3); + + const auto s1 = sqlgen::select_from("name"_c, "age"_c); + const auto s2 = sqlgen::select_from("name"_c, "age"_c); + const auto s3 = sqlgen::select_from("name"_c, "age"_c); + + const auto united = sqlgen::unite>(s1, s2, s3); + + const auto sel = sqlgen::select_from(united, "name"_c, "age"_c) | + sqlgen::to>; + + const auto result = sel(conn); + + const auto users = result.value(); + + const auto query = sqlgen::sqlite::to_sql(sel); + + EXPECT_EQ( + query, + R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")))"); + + EXPECT_EQ(users.size(), 3); + EXPECT_EQ(users.at(0).name, "Jane"); + EXPECT_EQ(users.at(0).age, 25); + EXPECT_EQ(users.at(1).name, "Joe"); + EXPECT_EQ(users.at(1).age, 40); + EXPECT_EQ(users.at(2).name, "John"); + EXPECT_EQ(users.at(2).age, 30); +} + +} // namespace test_union_in_select