// Copyright (c) 2022 Klemens D. Morgenstern // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_SQLITE_STATEMENT_HPP #define BOOST_SQLITE_STATEMENT_HPP #include #include #include #include #include #include #include #include BOOST_SQLITE_BEGIN_NAMESPACE struct connection; template struct static_resultset; /// @brief A reference to a value to temporary bind for an execute statement. Most values are captures by reference. /// @ingroup reference struct param_ref { /// Default construct a parameter, gives `null`. param_ref() = default; /// Bind null param_ref(variant2::monostate) : impl_{variant2::in_place_type_t{}} {} /// Bind null param_ref(std::nullptr_t) : impl_{variant2::in_place_type_t{}} {} /// Bind an integer. template::value>::type> param_ref(I value) { BOOST_IF_CONSTEXPR ((sizeof(I) == sizeof(int) && std::is_unsigned::value) || (sizeof(I) > sizeof(int))) impl_.emplace(static_cast(value)); else impl_.emplace(static_cast(value)); } /// Bind a blob. param_ref(blob_view blob) : impl_(blob) { } /// Bind a string. param_ref(string_view text) : impl_(text) { } template param_ref(StringLike && text, typename std::enable_if::value>::type * = nullptr) : impl_(variant2::in_place_type_t{}, text) {} template param_ref(BlobLike && text, typename std::enable_if< !std::is_constructible::value && std::is_constructible::value>::type * = nullptr) : impl_(variant2::in_place_type_t{}, text) {} /// Bind a floating point value. param_ref(double value) : impl_(value) { } /// Bind a zero_blob value, i.e. a blob that initialized by zero. param_ref(zero_blob zb) : impl_(zb) { } #if SQLITE_VERSION_NUMBER >= 3020000 /// Bind pointer value to the parameter. @see https://www.sqlite.org/bindptr.html template param_ref(std::unique_ptr ptr) : impl_(variant2::in_place_index_t<7>{}, std::unique_ptr( static_cast(ptr.release()), +[](void * ptr){delete static_cast(ptr);}), typeid(T).name()) { } /// Bind pointer value with a function as deleter to the parameter. @see https://www.sqlite.org/bindptr.html template param_ref(std::unique_ptr ptr) : impl_(variant2::in_place_index_t<7>{}, std::unique_ptr( static_cast(ptr.release()), +[](void * ptr){delete static_cast(ptr);}), typeid(T).name()) { } /// @brief Bind pointer value with a function custom deleter to the parameter. /// The deleter needs to be default constructible. @see https://www.sqlite.org/bindptr.html template param_ref(std::unique_ptr ptr, typename std::enable_if::value && std::is_default_constructible::value, int>::type * = nullptr) : impl_(variant2::in_place_index_t<7>{}, std::unique_ptr( static_cast(ptr.release()), +[](void * ptr){delete static_cast(ptr);}), typeid(T).name()) { } #endif /// Apply the param_ref to a statement. int apply(sqlite3_stmt * stmt, int c) const { return variant2::visit(visitor{stmt, c}, impl_); } private: struct make_visitor { template auto operator()(T&& t) const -> typename std::enable_if::value, param_ref>::type { return param_ref(std::forward(t)); } }; public: /// Construct param_ref from a variant template param_ref(T && t, decltype(variant2::visit(make_visitor(), std::forward(t))) * = nullptr) : param_ref(variant2::visit(make_visitor(), std::forward(t))) {} private: struct visitor { sqlite3_stmt * stmt; int col; int operator()(variant2::monostate ) { return sqlite3_bind_null(stmt, col); } int operator()(int i ) { return sqlite3_bind_int(stmt, col, i); } int operator()(sqlite3_int64 i64 ) { return sqlite3_bind_int64(stmt, col, i64); } int operator()(blob_view blob) { if (blob.size() > static_cast(std::numeric_limits::max())) return sqlite3_bind_blob64(stmt, col, blob.data(), blob.size(), SQLITE_STATIC); else return sqlite3_bind_blob(stmt, col, blob.data(), static_cast(blob.size()), SQLITE_STATIC); } int operator()(string_view text) { if (text.size() > std::numeric_limits::max()) return sqlite3_bind_text64(stmt, col, text.data(), text.size(), SQLITE_STATIC, SQLITE_UTF8); else return sqlite3_bind_text(stmt, col, text.data(), static_cast(text.size()), SQLITE_STATIC); } int operator()(double value) { return sqlite3_bind_double(stmt, col, value); } int operator()(zero_blob zb) { if (static_cast(zb) > static_cast(std::numeric_limits::max())) return sqlite3_bind_zeroblob64(stmt, col, static_cast(zb)); else return sqlite3_bind_zeroblob(stmt, col, static_cast(zb)); } #if SQLITE_VERSION_NUMBER >= 3020000 int operator()(std::pair, const char*> & p) { auto d =p.first.get_deleter(); return sqlite3_bind_pointer(stmt, col, p.first.release(), p.second, d); } #endif }; mutable // so we can use it with variant2::variant= 3020000 , std::pair, const char*> #endif > impl_; }; /** @brief A statement used for a prepared-statement. @ingroup reference */ struct statement { ///@{ /** @brief execute the prepared statement once. @param params The arguments to be passed to the prepared statement. This can be a map or a vector of param_ref. @param ec The system::error_code used to deliver errors for the exception less overload. @param info The error_info used to deliver errors for the exception less overload. @return The resultset of the query. @code{.cpp} extern sqlite::connection conn; statement st = conn.prepare("select id from users where name = $1;"); resultset q = std::move(st).execute(std::make_tuple("peter")); @endcode */ template > resultset execute( ArgRange && params, system::error_code& ec, error_info& info) && { bind_impl(std::forward(params), ec, info); resultset rs; rs.impl_.reset(impl_.release()); if (!ec) rs.read_next(ec, info); return rs; } template > resultset execute(ArgRange && params) && { system::error_code ec; error_info ei; auto tmp = std::move(*this).execute(std::forward(params), ec, ei); if (ec) detail::throw_error_code(ec, ei); return tmp; } resultset execute( std::initializer_list> params, system::error_code& ec, error_info& info) && { bind_impl(std::move(params), ec, info); resultset rs; rs.impl_.reset(impl_.release()); if (!ec) rs.read_next(ec, info); return rs; } resultset execute(std::initializer_list> params) && { system::error_code ec; error_info ei; auto tmp = std::move(*this).execute(std::move(params), ec, ei); if (ec) detail::throw_error_code(ec, ei); return tmp; } template> static_resultset execute( ArgRange && params, system::error_code & ec, error_info & ei) && { static_resultset tmp = std::move(*this).execute(std::forward(params), ec, ei); if (ec) return {}; tmp.check_columns_(ec, ei); if (ec) return {}; return tmp; } template> static_resultset execute(ArgRange && params) && { system::error_code ec; error_info ei; auto tmp = std::move(*this).execute(std::forward(params), ec, ei); if (ec) throw_exception(system::system_error(ec, ei.message())); return tmp; } template static_resultset execute( std::initializer_list> params, system::error_code & ec, error_info & ei) && { static_resultset tmp = std::move(*this).execute(std::move(params), ec, ei); if (ec) return {}; tmp.check_columns_(ec, ei); if (ec) return {}; return tmp; } template static_resultset execute(std::initializer_list> params) && { system::error_code ec; error_info ei; auto tmp = std::move(*this).execute(std::move(params), ec, ei); if (ec) throw_exception(system::system_error(ec, ei.message())); return tmp; } ///@} ///@{ /** @brief execute the prepared statement and reset it afterwards. @warning The handle is shared between the statement & resultset. The statemens need to be kept alive. @param params The arguments to be passed to the prepared statement. This can be a map, a vector or a stuple of param_ref. @param ec The system::error_code used to deliver errors for the exception less overload. @param info The error_info used to deliver errors for the exception less overload. @return The resultset of the query. @code{.cpp} extern sqlite::connection conn; statement st = conn.prepare("select id from users where name = $1;"); resultset q = std::move(st).execute(std::make_tuple("peter")); @endcode */ template > resultset execute( ArgRange && params, system::error_code& ec, error_info& info) & { bind_impl(std::forward(params), ec, info); resultset rs; rs.impl_.get_deleter().delete_ = false; rs.impl_.reset(impl_.get()); if (!ec) rs.read_next(ec, info); return rs; } template > resultset execute(ArgRange && params) & { system::error_code ec; error_info ei; auto tmp = execute(std::forward(params), ec, ei); if (ec) detail::throw_error_code(ec, ei); return tmp; } resultset execute( std::initializer_list> params, system::error_code& ec, error_info& info) & { bind_impl(std::move(params), ec, info); resultset rs; rs.impl_.get_deleter().delete_ = false; rs.impl_.reset(impl_.get()); if (!ec) rs.read_next(ec, info); return rs; } resultset execute(std::initializer_list> params) & { system::error_code ec; error_info ei; auto tmp = execute(std::move(params), ec, ei); if (ec) detail::throw_error_code(ec, ei); return tmp; } template> static_resultset execute( ArgRange && params, system::error_code & ec, error_info & ei) & { static_resultset tmp = execute(std::forward(params), ec, ei); if (ec) return {}; tmp.check_columns_(ec, ei); if (ec) return {}; return tmp; } template> static_resultset execute(ArgRange && params) & { system::error_code ec; error_info ei; auto tmp = execute(std::forward(params), ec, ei); if (ec) throw_exception(system::system_error(ec, ei.message())); return tmp; } template static_resultset execute( std::initializer_list> params, system::error_code & ec, error_info & ei) & { static_resultset tmp = execute(std::move(params), ec, ei); if (ec) return {}; tmp.check_columns_(ec, ei); if (ec) return {}; return tmp; } template static_resultset execute(std::initializer_list> params) & { system::error_code ec; error_info ei; auto tmp = execute(std::move(params), ec, ei); if (ec) throw_exception(system::system_error(ec, ei.message())); return tmp; } ///@} /// Returns the sql used to construct the prepared statement. core::string_view sql() { return sqlite3_sql(impl_.get()); } #if SQLITE_VERSION_NUMBER >= 3014000 /// Returns the expanded sql used to construct the prepared statement. core::string_view expanded_sql() { return sqlite3_expanded_sql(impl_.get()); } #endif /// Returns the expanded sql used to construct the prepared statement. #ifdef SQLITE_ENABLE_NORMALIZE core::string_view normalized_sql() { return sqlite3_normalized_sql(impl_.get()); } #endif /// Returns the declared type of the column core::string_view declared_type(int id) const { return sqlite3_column_decltype(impl_.get(), id); } private: template void bind_impl(std::tuple && vec, system::error_code & ec, error_info & ei) { const auto sz = sqlite3_bind_parameter_count(impl_.get()); if (sizeof...(Args) < static_cast(sz)) { BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_ERROR); ei.format("To few parameters provided. Needed %d got %ld", sz, sizeof...(Args)); return; } int i = 1, ar = SQLITE_OK; mp11::tuple_for_each(std::move(vec), [&](param_ref pr) { if (ar == SQLITE_OK) ar = pr.apply(impl_.get(), i++); }); if (ar != SQLITE_OK) { BOOST_SQLITE_ASSIGN_EC(ec, ar); ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get()))); return; } } template void bind_impl(const std::tuple & vec, system::error_code & ec, error_info & ei) { const auto sz = sqlite3_bind_parameter_count(impl_.get()); if (static_cast(sizeof...(Args)) < sz) { BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_ERROR); ei.format("To few parameters provided. Needed %d got %ld", sz, sizeof...(Args)); return; } int i = 1, ar = SQLITE_OK; mp11::tuple_for_each(std::move(vec), [&](param_ref pr) { if (ar == SQLITE_OK) ar = pr.apply(impl_.get(), i++); }); if (ar != SQLITE_OK) { BOOST_SQLITE_ASSIGN_EC(ec, ar); ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get()))); return; } } template void bind_impl(ParamVector && vec, system::error_code & ec, error_info & ei, typename std::enable_if::type::value_type, param_ref>::value>::type * = nullptr) { const auto sz = sqlite3_bind_parameter_count(impl_.get()); if (vec.size() < static_cast(sz)) { BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_ERROR); ei.format("To few parameters provided. Needed %d got %ld", sz, vec.size()); } int i = 1; for (const param_ref & pr : std::forward(vec)) { int ar = pr.apply(impl_.get(), i++); if (ar != SQLITE_OK) { BOOST_SQLITE_ASSIGN_EC(ec, ar); ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get()))); return; } } } template void bind_impl(ParamMap && vec, system::error_code & ec, error_info & ei, typename std::enable_if< std::is_convertible::type::key_type, string_view>::value && std::is_convertible::type::mapped_type, param_ref>::value >::type * = nullptr) { for (auto i = 1; i <= sqlite3_bind_parameter_count(impl_.get()); i ++) { auto c = sqlite3_bind_parameter_name(impl_.get(), i); if (c == nullptr) { BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISUSE); ei.set_message("Parameter maps require all parameters to be named."); return ; } auto itr = vec.find(c+1); if (itr == vec.end()) { BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISUSE); ei.format("Can't find value for key '%s'", c+1); return ; } int ar = SQLITE_OK; if (std::is_rvalue_reference::value) ar = param_ref(std::move(itr->second)).apply(impl_.get(), i); else ar = param_ref(itr->second).apply(impl_.get(), i); if (ar != SQLITE_OK) { BOOST_SQLITE_ASSIGN_EC(ec, ar); ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get()))); return; } } } void bind_impl(std::initializer_list> params, system::error_code & ec, error_info & ei) { for (auto i = 1; i <= sqlite3_bind_parameter_count(impl_.get()); i ++) { auto c = sqlite3_bind_parameter_name(impl_.get(), i); if (c == nullptr) { BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISUSE); ei.set_message("Parameter maps require all parameters to be named."); return ; } auto itr = std::find_if(params.begin(), params.end(), [&](const std::pair & p) { return p.first == (c+1); }); if (itr == params.end()) { BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISUSE); ei.format("Can't find value for key '%s'", c+1); return ; } auto ar = itr->second.apply(impl_.get(), i); if (ar != SQLITE_OK) { BOOST_SQLITE_ASSIGN_EC(ec, ar); ei.set_message(sqlite3_errmsg(sqlite3_db_handle(impl_.get()))); return; } } } friend struct connection; struct deleter_ { void operator()(sqlite3_stmt * sm) { sqlite3_finalize(sm); } }; std::unique_ptr impl_; }; BOOST_SQLITE_END_NAMESPACE #endif //BOOST_SQLITE_STATEMENT_HPP