Skip to content

Custom data types #106

@matthijs

Description

@matthijs

I am looking into migrating to sqlpp23. I found one issue.

I am using a lot of strong types. Mapping enum fields in the database to enum fields in C++, strong typed id's, so that mistakes like: tableA_id == tableB_id cannot happen. Well, a small reproducible example says more then words:

#include <sqlpp23/mock_db/mock_db.h>
#include <sqlpp23/sqlpp23.h>
#include <sqlpp23/tests/core/make_test_connection.h>

// Strong type
struct TestId {
    int64_t value{0};
    explicit TestId(int64_t v) : value(v) {}
    TestId() = default;
    bool operator==(const TestId&) const = default;
};


// Table definition
struct Test_ {
    struct Id {
        SQLPP_CREATE_NAME_TAG_FOR_SQL_AND_CPP(id, id);
        using data_type   = TestId;           // <-- strong type as data_type
        using has_default = std::false_type;
    };
    struct Symbol {
        SQLPP_CREATE_NAME_TAG_FOR_SQL_AND_CPP(symbol, symbol);
        using data_type   = ::sqlpp::text;
        using has_default = std::false_type;
    };
    SQLPP_CREATE_NAME_TAG_FOR_SQL_AND_CPP(stock, stock);
    template<typename T>
    using _table_columns = sqlpp::table_columns<T, Id, Symbol>;
    using _required_insert_columns = sqlpp::detail::type_set<
        sqlpp::column_t<sqlpp::table_t<Test_>, Id>,
        sqlpp::column_t<sqlpp::table_t<Test_>, Symbol>>;
};
using Test = ::sqlpp::table_t<Test_>;


// Glue needed for sqlpp
template <>
struct sqlpp::data_type_of<TestId> {
    using type = sqlpp::integral;
};

template <typename Context>
auto to_sql_string(Context& ctx, const TestId& id) {
    return to_sql_string(ctx, id.value);
}

template <>
struct sqlpp::result_data_type_of<TestId> {
    using type = TestId;    // result row will hold a TestId directly
};
// End glue


int main() {
    auto db = sqlpp::mock_db::make_test_connection({});
    const Test stock;

    for (const auto& row : db(select(all_of(stock)).from(stock))) {
        TestId id = row.id;
        (void)id;
    }

    return 0;
}

Compiling with: clang++ -std=c++23 -I include -I tests/include test.cpp gave the following output:

In file included from test.cpp:1:
In file included from include/sqlpp23/mock_db/mock_db.h:30:
In file included from include/sqlpp23/mock_db/database/connection.h:32:
include/sqlpp23/core/query/result_row.h:55:12: error: no matching member function for call to
      'read_field'
   55 |     target.read_field(index, _field::operator()());
      |     ~~~~~~~^~~~~~~~~~
include/sqlpp23/core/query/result_row.h:72:36: note: in instantiation of function template
      specialization 'sqlpp::detail::result_field<0, sqlpp::field_spec_t<Test_::Id::_sqlpp_name_tag,
      TestId>>::_read_field<sqlpp::mock_db::text_result_t>' requested here
   72 |     (result_field<Is, FieldSpecs>::_read_field(target), ...);
      |                                    ^
include/sqlpp23/core/query/result_row.h:134:12: note: in instantiation of function template
      specialization 'sqlpp::detail::result_row_impl<std::integer_sequence<unsigned long, 0, 1>,
      sqlpp::field_spec_t<Test_::Id::_sqlpp_name_tag, TestId>, sqlpp::field_spec_t<Test_::Symbol::_sqlpp_name_tag,
      sqlpp::text>>::_read_fields<sqlpp::mock_db::text_result_t>' requested here
  134 |     _impl::_read_fields(target);
      |            ^
include/sqlpp23/core/query/result_row.h:151:9: note: in instantiation of function template
      specialization 'sqlpp::result_row_t<sqlpp::field_spec_t<Test_::Id::_sqlpp_name_tag, TestId>,
      sqlpp::field_spec_t<Test_::Symbol::_sqlpp_name_tag, sqlpp::text>>::_read_fields<sqlpp::mock_db::text_result_t>'
      requested here
  151 |     row._read_fields(target);
      |         ^
include/sqlpp23/mock_db/text_result.h:92:42: note: in instantiation of function template specialization
      'sqlpp::detail::result_row_bridge::read_fields<sqlpp::field_spec_t<Test_::Id::_sqlpp_name_tag, TestId>,
      sqlpp::field_spec_t<Test_::Symbol::_sqlpp_name_tag, sqlpp::text>, sqlpp::mock_db::text_result_t>' requested here
   92 |       sqlpp::detail::result_row_bridge{}.read_fields(result_row, *this);
      |                                          ^
include/sqlpp23/core/result.h:70:13: note: in instantiation of function template specialization
      'sqlpp::mock_db::text_result_t::next<sqlpp::result_row_t<sqlpp::field_spec_t<Test_::Id::_sqlpp_name_tag, TestId>,
      sqlpp::field_spec_t<Test_::Symbol::_sqlpp_name_tag, sqlpp::text>>>' requested here
   70 |     _result.next(_result_row);
      |             ^
include/sqlpp23/core/clause/select_column_list.h:166:12: note: in instantiation of member function
      'sqlpp::result_t<sqlpp::mock_db::text_result_t, sqlpp::result_row_t<sqlpp::field_spec_t<Test_::Id::_sqlpp_name_tag,
      TestId>, sqlpp::field_spec_t<Test_::Symbol::_sqlpp_name_tag, sqlpp::text>>>::result_t' requested here
  166 |     return {statement_handler_t{}.select(std::forward<Statement>(self), db)};
      |            ^
include/sqlpp23/core/query/statement_handler.h:38:47: note: in instantiation of function template
      specialization 'sqlpp::select_result_methods_t<sqlpp::column_t<sqlpp::table_t<Test_>, Test_::Id>,
      sqlpp::column_t<sqlpp::table_t<Test_>, Test_::Symbol>>::_run<const sqlpp::statement_t<sqlpp::select_t,
      sqlpp::select_column_list_t<std::tuple<>, std::tuple<sqlpp::column_t<sqlpp::table_t<Test_>, Test_::Id>,
      sqlpp::column_t<sqlpp::table_t<Test_>, Test_::Symbol>>>, sqlpp::from_t<sqlpp::table_t<Test_>>, sqlpp::no_where_t,
      sqlpp::no_group_by_t, sqlpp::no_having_t, sqlpp::no_order_by_t, sqlpp::no_limit_t, sqlpp::no_offset_t,
      sqlpp::no_union_t, sqlpp::no_for_update_t> &, sqlpp::mock_db::connection_base>' requested here
   38 |     return std::forward<Statement>(statement)._run(db);
      |                                               ^
include/sqlpp23/mock_db/text_result.h:106:8: note: candidate function not viable: no known conversion
      from 'TestId' to 'bool &' for 2nd argument
  106 |   void read_field(size_t index, bool& value) {
      |        ^                        ~~~~~~~~~~~
include/sqlpp23/mock_db/text_result.h:111:8: note: candidate function not viable: no known conversion
      from 'TestId' to 'double &' for 2nd argument
  111 |   void read_field(size_t index, double& value) {
      |        ^                        ~~~~~~~~~~~~~
include/sqlpp23/mock_db/text_result.h:115:8: note: candidate function not viable: no known conversion
      from 'TestId' to 'int64_t &' (aka 'long &') for 2nd argument
  115 |   void read_field(size_t index, int64_t& value) {
      |        ^                        ~~~~~~~~~~~~~~
include/sqlpp23/mock_db/text_result.h:119:8: note: candidate function not viable: no known conversion
      from 'TestId' to 'uint64_t &' (aka 'unsigned long &') for 2nd argument
  119 |   void read_field(size_t index, uint64_t& value) {
      |        ^                        ~~~~~~~~~~~~~~~
include/sqlpp23/mock_db/text_result.h:123:8: note: candidate function not viable: no known conversion
      from 'TestId' to 'std::span<const uint8_t> &' (aka 'span<const unsigned char> &') for 2nd argument
  123 |   void read_field(size_t index, std::span<const uint8_t>& value) {
      |        ^                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/sqlpp23/mock_db/text_result.h:129:8: note: candidate function not viable: no known conversion
      from 'TestId' to 'std::string_view &' (aka 'basic_string_view<char> &') for 2nd argument
  129 |   void read_field(size_t index, std::string_view& value) {
      |        ^                        ~~~~~~~~~~~~~~~~~~~~~~~
include/sqlpp23/mock_db/text_result.h:134:8: note: candidate function not viable: no known conversion
      from 'TestId' to 'std::chrono::sys_days &' (aka 'time_point<std::chrono::system_clock, duration<long, ratio<86400>>>
      &') for 2nd argument
  134 |   void read_field(size_t index, std::chrono::sys_days& value) {
      |        ^                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/sqlpp23/mock_db/text_result.h:162:8: note: candidate function not viable: no known conversion
      from 'TestId' to '::sqlpp::chrono::sys_microseconds &' (aka 'time_point<std::chrono::system_clock, duration<long,
      ratio<1, 1000000>>> &') for 2nd argument
  162 |   void read_field(size_t index, ::sqlpp::chrono::sys_microseconds& value) {
      |        ^                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/sqlpp23/mock_db/text_result.h:190:8: note: candidate function not viable: no known conversion
      from 'TestId' to '::std::chrono::microseconds &' (aka 'duration<long, ratio<1, 1000000>> &') for 2nd argument
  190 |   void read_field(size_t index, ::std::chrono::microseconds& value) {
      |        ^                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/sqlpp23/mock_db/text_result.h:219:8: note: candidate template ignored: could not match
      'std::optional<T>' against 'TestId'
  219 |   auto read_field(size_t index, std::optional<T>& value) -> void {
      |        ^
1 error generated.

As you can see in the example, I am implementing the to_sql_string function, I expected something like a from_sql_string to do the other way around.

With sqlpp11 I had implementation of different data_types which handled exactly this. Hopefully this functionality can be implemented in sqlpp23!

Thank you for the wonderful library!

Regards, Matthijs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions