diff --git a/include/sqlpp11/column.h b/include/sqlpp11/column.h index ee487724..8915677f 100644 --- a/include/sqlpp11/column.h +++ b/include/sqlpp11/column.h @@ -86,6 +86,11 @@ namespace sqlpp return {*this}; } + sqlpp::compat::optional if_(bool condition) const + { + return condition ? sqlpp::compat::make_optional(*this) : sqlpp::compat::nullopt; + } + template auto operator=(T t) const -> assignment_t> { diff --git a/include/sqlpp11/compat/optional.h b/include/sqlpp11/compat/optional.h index 35ed5165..044df019 100644 --- a/include/sqlpp11/compat/optional.h +++ b/include/sqlpp11/compat/optional.h @@ -45,6 +45,8 @@ namespace sqlpp using std::nullopt; using std::bad_optional_access; + using std::make_optional; + } // namespace compat } // namespace sqlpp @@ -249,6 +251,12 @@ namespace sqlpp return !right; } + template + constexpr optional::type> make_optional(T&& value) + { + return optional::type>(std::forward(value)); + } + } // namespace compat } // namespace sqlpp diff --git a/include/sqlpp11/field_spec.h b/include/sqlpp11/field_spec.h index 48a6586e..7ba5ae83 100644 --- a/include/sqlpp11/field_spec.h +++ b/include/sqlpp11/field_spec.h @@ -76,13 +76,14 @@ namespace sqlpp template struct make_field_spec_impl { - static constexpr bool _can_be_null = can_be_null_t::value; + using RawNamedExpr = remove_optional_t; + static constexpr bool _can_be_null = is_optional::value or can_be_null_t::value; static constexpr bool _depends_on_outer_table = - detail::make_intersect_set_t, + detail::make_intersect_set_t, typename Select::_used_outer_tables>::size::value > 0; - using type = field_spec_t, + using type = field_spec_t, logic::any_t<_can_be_null, _depends_on_outer_table>::value>; }; } // namespace detail diff --git a/include/sqlpp11/select_column_list.h b/include/sqlpp11/select_column_list.h index 8f9dc358..bce56ad7 100644 --- a/include/sqlpp11/select_column_list.h +++ b/include/sqlpp11/select_column_list.h @@ -55,36 +55,44 @@ namespace sqlpp template struct select_traits { - using _traits = make_traits, + using _traits = make_traits>, tag::is_select_column_list, tag::is_return_value, tag::is_expression, tag::is_selectable>; - using _alias_t = typename Column::_alias_t; + using _alias_t = typename remove_optional_t::_alias_t; }; } // namespace detail - // SELECTED COLUMNS DATA - template - struct select_column_list_data_t + template + struct select_column_t { - select_column_list_data_t(Columns... columns) : _columns(columns...) + constexpr select_column_t(Column column) : _column(column) { } - select_column_list_data_t(std::tuple columns) : _columns(columns) - { - } + constexpr select_column_t(const select_column_t&) = default; + constexpr select_column_t(select_column_t&&) = default; + select_column_t& operator=(const select_column_t&) = default; + select_column_t& operator=(select_column_t&&) = default; + ~select_column_t() = default; - select_column_list_data_t(const select_column_list_data_t&) = default; - select_column_list_data_t(select_column_list_data_t&&) = default; - select_column_list_data_t& operator=(const select_column_list_data_t&) = default; - select_column_list_data_t& operator=(select_column_list_data_t&&) = default; - ~select_column_list_data_t() = default; - - std::tuple _columns; + Column _column; }; + template + Context& serialize(const select_column_t& t, Context& context) + { + if (has_value(t._column)) + { + return serialize(get_value(t._column), context); + } + + context << "NULL AS " << name_of>::template char_ptr(); + return context; + } + + SQLPP_PORTABLE_STATIC_ASSERT( assert_no_unknown_tables_in_selected_columns_t, "at least one selected column requires a table which is otherwise not known in the statement"); @@ -103,7 +111,7 @@ namespace sqlpp using _alias_t = typename detail::select_traits::_alias_t; - using _data_t = select_column_list_data_t; + using _data_t = std::tuple...>; struct _column_type { @@ -231,8 +239,9 @@ namespace sqlpp template struct check_selected_columns { - using type = static_combined_check_t< - static_check_t::value...>::value, assert_selected_colums_are_selectable_t>>; + using type = + static_combined_check_t>::value...>::value, + assert_selected_colums_are_selectable_t>>; }; template using check_selected_columns_t = typename check_selected_columns::type; @@ -299,9 +308,9 @@ namespace sqlpp // Interpreters template - Context& serialize(const select_column_list_data_t& t, Context& context) + Context& serialize(const std::tuple...>& t, Context& context) { - interpret_tuple(t._columns, ',', context); + interpret_tuple(t, ',', context); return context; } diff --git a/include/sqlpp11/type_traits.h b/include/sqlpp11/type_traits.h index 9769c600..f67b5458 100644 --- a/include/sqlpp11/type_traits.h +++ b/include/sqlpp11/type_traits.h @@ -28,6 +28,8 @@ #include #include + +#include #include #include #include @@ -37,6 +39,55 @@ namespace sqlpp { + template + struct is_optional : public std::false_type + { + }; + + template + struct is_optional> : public std::true_type + { + }; + + template + struct remove_optional + { + using type = T; + }; + + template + struct remove_optional> + { + using type = T; + }; + + template + const T& get_value(const T& t) + { + return t; + } + + template + const T& get_value(const sqlpp::compat::optional& t) + { + return t.value(); + } + + template + auto has_value(const T&) -> bool + { + return true; + } + + template + auto has_value(const sqlpp::compat::optional& t) -> bool + { + return t.has_value(); + } + + template + using remove_optional_t = typename remove_optional::type; + struct no_value_t; namespace detail { diff --git a/tests/core/serialize/SelectColumns.cpp b/tests/core/serialize/SelectColumns.cpp index 961aaf23..7f2a7a8a 100644 --- a/tests/core/serialize/SelectColumns.cpp +++ b/tests/core/serialize/SelectColumns.cpp @@ -39,21 +39,37 @@ int SelectColumns(int, char*[]) compare(__LINE__, select(foo.doubleN, bar.id), "SELECT tab_foo.double_n,tab_bar.id"); // All columns of a table - compare(__LINE__, select(all_of(foo)), "SELECT tab_foo.id,tab_foo.text_nn_d,tab_foo.int_n,tab_foo.double_n,tab_foo.u_int_n,tab_foo.blob_n"); + compare(__LINE__, select(all_of(foo)), + "SELECT tab_foo.id,tab_foo.text_nn_d,tab_foo.int_n,tab_foo.double_n,tab_foo.u_int_n,tab_foo.blob_n"); // All columns of a table plus one more - compare(__LINE__, select(all_of(foo), bar.id), "SELECT tab_foo.id,tab_foo.text_nn_d,tab_foo.int_n,tab_foo.double_n,tab_foo.u_int_n,tab_foo.blob_n,tab_bar.id"); + compare( + __LINE__, select(all_of(foo), bar.id), + "SELECT tab_foo.id,tab_foo.text_nn_d,tab_foo.int_n,tab_foo.double_n,tab_foo.u_int_n,tab_foo.blob_n,tab_bar.id"); // One more, plus all columns of a table - compare(__LINE__, select(bar.id, all_of(foo)), "SELECT tab_bar.id,tab_foo.id,tab_foo.text_nn_d,tab_foo.int_n,tab_foo.double_n,tab_foo.u_int_n,tab_foo.blob_n"); + compare( + __LINE__, select(bar.id, all_of(foo)), + "SELECT tab_bar.id,tab_foo.id,tab_foo.text_nn_d,tab_foo.int_n,tab_foo.double_n,tab_foo.u_int_n,tab_foo.blob_n"); // Column and aggregate function compare(__LINE__, select(foo.doubleN, count(bar.id)), "SELECT tab_foo.double_n,COUNT(tab_bar.id) AS count_"); // Column aliases - compare(__LINE__, select(foo.doubleN.as(sqlpp::alias::o), count(bar.id).as(sqlpp::alias::a)), "SELECT tab_foo.double_n AS o,COUNT(tab_bar.id) AS a"); + compare(__LINE__, select(foo.doubleN.as(sqlpp::alias::o), count(bar.id).as(sqlpp::alias::a)), + "SELECT tab_foo.double_n AS o,COUNT(tab_bar.id) AS a"); -#warning: add optional column tests + // Optional column manually + compare(__LINE__, select(sqlpp::compat::make_optional(bar.id)), "SELECT tab_bar.id"); + auto opt_id = sqlpp::compat::make_optional(bar.id); + opt_id.reset(); + compare(__LINE__, select(opt_id), "SELECT NULL AS id"); + + // Optional column using `if_` + compare(__LINE__, select(bar.id.if_(true)), "SELECT tab_bar.id"); + compare(__LINE__, select(bar.id.if_(false)), "SELECT NULL AS id"); + +#warning: add more optional column tests return 0; } diff --git a/tests/core/types/result_row.cpp b/tests/core/types/result_row.cpp index 2bf78f6e..48e7ad90 100644 --- a/tests/core/types/result_row.cpp +++ b/tests/core/types/result_row.cpp @@ -55,6 +55,21 @@ namespace } } + void optional_columns() + { + { + // result fields are as nullable as the expressions they represent +#warning: add `if_` to other expressions, too +#warning: test with nullable columns, too. +#warning: test with all kinds of functions as well. +#warning: We should actually test for the exact type! + const auto rows = db(select(bar.id.if_(true), bar.boolNn.if_(true)/*, seven*/).from(bar).unconditionally()); + auto& x = rows.front(); + static_assert(is_optional::value, ""); + static_assert(is_optional::value, ""); + } + } + void join() { // Join @@ -233,6 +248,7 @@ namespace int main(int, char* []) { single_table(); + optional_columns(); join(); aggregates(); }