diff options
author | John Turner <jturner.usa@gmail.com> | 2025-09-14 00:16:10 -0400 |
---|---|---|
committer | John Turner <jturner.usa@gmail.com> | 2025-09-14 00:16:10 -0400 |
commit | efcea3a80da7c4479d5fe168435ecc9fd06bdc72 (patch) | |
tree | 5cb0177e17b1b00a177f2e830e809f606334571b /include/boost/sqlite | |
download | sqlite-kv-bench-efcea3a80da7c4479d5fe168435ecc9fd06bdc72.tar.gz |
Squashed 'subprojects/boost-sqlite/' content from commit 3378e35
git-subtree-dir: subprojects/boost-sqlite
git-subtree-split: 3378e353705271e569cf4ba15c467b840a39798c
Diffstat (limited to 'include/boost/sqlite')
31 files changed, 6452 insertions, 0 deletions
diff --git a/include/boost/sqlite/allocator.hpp b/include/boost/sqlite/allocator.hpp new file mode 100644 index 0000000..f449475 --- /dev/null +++ b/include/boost/sqlite/allocator.hpp @@ -0,0 +1,48 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_ALLOCATOR_HPP +#define BOOST_SQLITE_ALLOCATOR_HPP + +#include <boost/sqlite/detail/config.hpp> + +#include <cstddef> +#include <cstdint> + +BOOST_SQLITE_BEGIN_NAMESPACE + +template<typename T> +struct allocator +{ + constexpr allocator() noexcept {} + constexpr allocator( const allocator& other ) noexcept {} + template< class U > + constexpr allocator( const allocator<U>& other ) noexcept {} + +#if defined(SQLITE_4_BYTE_ALIGNED_MALLOC) + constexpr static std::size_t alignment = 4u; +#else + constexpr static std::size_t alignment = 8u; +#endif + + static_assert(alignof(T) <= alignment, "T alignment can't be fulfilled by sqlite"); + [[nodiscard]] T* allocate( std::size_t n ) + { + auto p = static_cast<T*>(sqlite3_malloc64(n * sizeof(T))); + if (p == nullptr) + boost::throw_exception(std::bad_alloc()); + return p; + } + void deallocate( T* p, std::size_t) + { + return sqlite3_free(p); + } +}; + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_ALLOCATOR_HPP diff --git a/include/boost/sqlite/backup.hpp b/include/boost/sqlite/backup.hpp new file mode 100644 index 0000000..737a28e --- /dev/null +++ b/include/boost/sqlite/backup.hpp @@ -0,0 +1,74 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_BACKUP_HPP +#define BOOST_SQLITE_BACKUP_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/cstring_ref.hpp> +#include <boost/sqlite/error.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + +struct connection ; + +///@{ +/** + @brief Backup a database + @ingroup reference + + This function will create a backup of an existing database. + This can be useful to write an in memory database to disk et vice versa. + + @param source The source database to backup + @param target The target of the backup + @param source_name The source database to read the backup from. Default is 'main'. + @param target_name The target database to write the backup to. Default is 'main'. + + @par Error Handling + + @throws system_error from overload without `ec` & `ei` + + or you need to pass + + @param ec The system::error_code to capture any possibly errors + @param ei Additional error_info when error occurs. + + @par Example + + @code{.cpp} + + sqlite::connection conn{":memory:"}; + { + sqlite::connection read{"./read_only_db.db", SQLITE_READONLY}; + backup(read, target); + } + + @endcode + */ +BOOST_SQLITE_DECL +void +backup(connection & source, + connection & target, + cstring_ref source_name, + cstring_ref target_name, + system::error_code & ec, + error_info & ei); + +BOOST_SQLITE_DECL +void +backup(connection & source, + connection & target, + cstring_ref source_name = "main", + cstring_ref target_name = "main"); + +///@} + +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_BACKUP_HPP diff --git a/include/boost/sqlite/blob.hpp b/include/boost/sqlite/blob.hpp new file mode 100644 index 0000000..cd82363 --- /dev/null +++ b/include/boost/sqlite/blob.hpp @@ -0,0 +1,156 @@ +// 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_BLOB_HPP +#define BOOST_SQLITE_BLOB_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/memory.hpp> +#include <boost/sqlite/error.hpp> +#include <type_traits> +#include <memory> +#include <cstring> + +BOOST_SQLITE_BEGIN_NAMESPACE + +struct connection ; + +/// @brief a view to a binary large object @ingroup reference +struct blob_view +{ + /// The data in the blob + const void * data() const {return data_;} + /// The size of the data int he blob, in bytes + std::size_t size() const {return size_;} + /// Construct a blob + blob_view(const void * data, std::size_t size) : data_(data), size_(size) {} + + /// Construct an empty blob + blob_view() = default; + /// Construct a blob from some other blob-like structure. + template<typename T> + explicit blob_view(const T & value, + typename std::enable_if< + std::is_pointer<decltype(std::declval<T>().data())>::value + && std::is_convertible<decltype(std::declval<T>().size()), std::size_t>::value + >::type * = nullptr) : data_(value.data()), size_(value.size()) {} + + inline blob_view(const struct blob & b); + private: + const void * data_{nullptr}; + std::size_t size_{0u}; +}; + +/// @brief Helper type to pass a blob full of zeroes without allocating extra memory. @ingroup reference +enum class zero_blob : sqlite3_uint64 {}; + +/// @brief An object that owns a binary large object. @ingroup reference +struct blob +{ + /// The data in the blob + void * data() const {return impl_.get();} + /// The size of the data int he blob, in bytes + std::size_t size() const {return size_;} + + /// Create a blob from a blob_view + explicit blob(blob_view bv) + { + impl_.reset(sqlite3_malloc(static_cast<int>(bv.size()))); + size_ = bv.size(); + std::memcpy(impl_.get(), bv.data(), size_); + } + /// Create an empty blob with size `n`. + explicit blob(std::size_t n) : impl_(sqlite3_malloc(static_cast<int>(n))), size_(n) {} + + /// Construct an empty blob + constexpr blob() = default; + /// Release & take ownership of the blob. + void * release() && {return std::move(impl_).release(); } + private: + unique_ptr<void> impl_; + std::size_t size_{0u}; +}; + +blob_view::blob_view(const blob & b) : data_(b.data()), size_(b.size()) {} + +/// @brief an object that holds a binary large object. Can be obtained by using @ref blob_handle. @ingroup reference +struct blob_handle +{ + /// Default constructor + blob_handle() = default; + + /// Construct from a handle. Takes owner ship + explicit blob_handle(sqlite3_blob * blob) : blob_(blob) {} + + ///@{ + /// @brief Reopen on another row + BOOST_SQLITE_DECL + void reopen(sqlite3_int64 row_id, system::error_code & ec); + BOOST_SQLITE_DECL + void reopen(sqlite3_int64 row_id); + ///@} + + ///@{ + /// @brief Read data from the blob + BOOST_SQLITE_DECL + void read_at(void *data, int len, int offset, system::error_code &ec); + BOOST_SQLITE_DECL + void read_at(void *data, int len, int offset); + ///@} + + ///@{ + /// @brief Write data to the blob + BOOST_SQLITE_DECL + void write_at(const void *data, int len, int offset, system::error_code &ec); + BOOST_SQLITE_DECL + void write_at(const void *data, int len, int offset); + ///@} + + /// The size of the blob + std::size_t size() const {return static_cast<std::size_t>(sqlite3_blob_bytes(blob_.get()));} + + /// The handle of the blob + using handle_type = sqlite3_blob*; + /// Returns the handle of the blob + handle_type handle() { return blob_.get(); } + /// Release the owned handle. + handle_type release() && { return blob_.release(); } + private: + struct deleter_ + { + void operator()(sqlite3_blob *impl) + { + sqlite3_blob_close(impl); + } + }; + std::unique_ptr<sqlite3_blob, deleter_> blob_; +}; + +///@{ +/// Open a blob for incremental access +BOOST_SQLITE_DECL +blob_handle open_blob(connection & conn, + cstring_ref db, + cstring_ref table, + cstring_ref column, + sqlite3_int64 row, + bool read_only, + system::error_code &ec, + error_info &ei); + +BOOST_SQLITE_DECL +blob_handle open_blob(connection & conn, + cstring_ref db, + cstring_ref table, + cstring_ref column, + sqlite3_int64 row, + bool read_only = false); +///}@ + + + +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_BLOB_HPP diff --git a/include/boost/sqlite/collation.hpp b/include/boost/sqlite/collation.hpp new file mode 100644 index 0000000..a4624c4 --- /dev/null +++ b/include/boost/sqlite/collation.hpp @@ -0,0 +1,140 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_COLLATION_HPP +#define BOOST_SQLITE_COLLATION_HPP + +#include <boost/sqlite/connection.hpp> +#include <boost/sqlite/detail/exception.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + +///@{ +/** Define a custom collation function + @ingroup reference + + @param conn A connection to the database in which to install the collation. + @param name The name of the collation. + @param func The function + + The function must be callable with two `string_view` and return an int, indicating the comparison results. + + @par Example + + @code{.cpp} + + // a case insensitive string omparison, e.g. from boost.urls + int ci_compare(string_view s0, string_view s1) noexcept; + + extern sqlite::connection conn; + + // Register the collation + sqlite::create_collation(conn, "iequal", &ci_compare); + + // use the collation to get by name, case insensitively + conn.query("select first_name, last_name from people where first_name = 'Klemens' collate iequal;"); + + // order by names case insensitively + conn.query("select * from people order by last_name collate iequal asc;"); + + @endcode + + */ + +template<typename Func> +void create_collation( + connection & conn, + cstring_ref name, + Func && func, + typename std::enable_if< + std::is_convertible< + decltype(func(string_view(), string_view())), + int>::value, + system::error_code>::type & ec) +{ + using func_type = typename std::decay<Func>::type; + unique_ptr<func_type> f{new (memory_tag{}) func_type(std::forward<Func>(func))}; + if (f == nullptr) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_NOMEM); + return; + } + auto res = sqlite3_create_collation_v2( + conn.handle(), + name.c_str(), + SQLITE_UTF8, + f.get(), + +[](void * data, int len_l, const void * str_l, int len_r, const void * str_r) -> int + { + string_view l(static_cast<const char*>(str_l), len_l); + string_view r(static_cast<const char*>(str_r), len_r); + auto & impl = (*static_cast<func_type*>(data)); + static_assert(noexcept(impl(l, r)), + "Collation function must be noexcept"); + return impl(l, r); + }, + +[](void * p) { delete_(static_cast<func_type*>(p)); } + ); + + if (res != SQLITE_OK) + BOOST_SQLITE_ASSIGN_EC(ec, res); + else + f.release(); +} + + +template<typename Func> +auto create_collation( + connection & conn, + cstring_ref name, + Func && func) +#if !defined(BOOST_SQLITE_GENERATING_DOCS) + -> typename std::enable_if< + std::is_convertible< + decltype(func(string_view(), string_view())), + int>::value>::type +#endif +{ + system::error_code ec; + create_collation(conn, name, std::forward<Func>(func), ec); + if (ec) + detail::throw_error_code(ec, BOOST_CURRENT_LOCATION); +} + + +inline void delete_collation( + connection & conn, + cstring_ref name, + system::error_code & ec) +{ + auto res = sqlite3_create_collation_v2( + conn.handle(), + name.c_str(), + SQLITE_UTF8, + nullptr, nullptr, nullptr); + if (res != SQLITE_OK) + { + BOOST_SQLITE_ASSIGN_EC(ec, res); + } +} + + +inline auto delete_collation( + connection & conn, + cstring_ref name) +{ + system::error_code ec; + delete_collation(conn, name, ec); + if (ec) + detail::throw_error_code(ec, BOOST_CURRENT_LOCATION); +} + +/// @} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_COLLATION_HPP diff --git a/include/boost/sqlite/connection.hpp b/include/boost/sqlite/connection.hpp new file mode 100644 index 0000000..0f6ccf8 --- /dev/null +++ b/include/boost/sqlite/connection.hpp @@ -0,0 +1,210 @@ +// 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_CONNECTION_HPP +#define BOOST_SQLITE_CONNECTION_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/error.hpp> +#include <boost/sqlite/resultset.hpp> +#include <boost/sqlite/statement.hpp> +#include <memory> +#include <boost/system/system_error.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + +template<typename T, bool Strict> +struct static_resultset; + +constexpr static cstring_ref in_memory = ":memory:"; + +/** @brief main object for a connection to a database. + @ingroup reference + + @par Example + @code{.cpp} + sqlite::connection conn; + conn.connect("./my-database.db"); + conn.prepare("insert into log (text) values ($1)").execute(std::make_tuple("booting up")); + @endcode + + */ +struct connection +{ + /// The handle of the connection + using handle_type = sqlite3*; + /// Returns the handle + handle_type handle() const { return impl_.get(); } + /// Release the owned handle. + handle_type release() && { return impl_.release(); } + + ///Default constructor + connection() = default; + /// Construct the connection from a handle. + explicit connection(handle_type handle, bool take_ownership = true) : impl_(handle, take_ownership) {} + /// Move constructor. + connection(connection && ) = default; + /// Move assign operator. + connection& operator=(connection && ) = default; + + /// Construct a connection and connect it to `filename`. `flags` is set by `SQLITE_OPEN_*` flags. @see https://www.sqlite.org/c3ref/c_open_autoproxy.html + connection(cstring_ref filename, + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) { connect(filename, flags); } + +#if defined(BOOST_WINDOWS_API) + template<typename Path, + typename = std::enable_if_t< + std::is_same<typename Path::string_type, std::wstring>::value && + std::is_constructible<cstring_ref, decltype(std::declval<Path>().string())>::value + >> + explicit connection(const Path & pth) : connection(pth.string()) {} +#endif + ///@{ + /// Connect the database to `filename`. `flags` is set by `SQLITE_OPEN_*` flags. + BOOST_SQLITE_DECL void connect(cstring_ref filename, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + BOOST_SQLITE_DECL void connect(cstring_ref filename, int flags, system::error_code & ec); + ///@} + +#if defined(BOOST_WINDOWS_API) + template<typename Path, + typename = std::enable_if_t< + std::is_same<typename Path::string_type, std::wstring>::value && + std::is_constructible<cstring_ref, decltype(std::declval<Path>().string())>::value + >> + void connect(const Path & pth, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) + { + connect(pth.string(), flags); + } + + + template<typename Path, + typename = std::enable_if_t< + std::is_same<typename Path::string_type, std::wstring>::value && + std::is_constructible<cstring_ref, decltype(std::declval<Path>().string())>::value + >> + void connect(const Path & pth, int flags, system::error_code & ec) + { + connect(pth.string(), flags, ec); + } +#endif + + ///@{ + /// Close the database connection. + BOOST_SQLITE_DECL void close(); + BOOST_SQLITE_DECL void close(system::error_code & ec, error_info & ei); + ///@} + + /// Check if the database holds a valid handle. + bool valid() const {return impl_ != nullptr;} + + + ///@{ + /// Perform a query without parameters. Can only execute a single statement. + BOOST_SQLITE_DECL resultset query( + core::string_view q, + system::error_code & ec, + error_info & ei); + + BOOST_SQLITE_DECL resultset query(core::string_view q); + + template<typename T, bool Strict = false> + static_resultset<T, Strict> query( + core::string_view q, + system::error_code & ec, + error_info & ei) + { + static_resultset<T, Strict> tmp = query(q, ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template<typename T, bool Strict = false> + static_resultset<T, Strict> query(core::string_view q) + { + system::error_code ec; + error_info ei; + auto tmp = query<T, Strict>(q, ec, ei); + if (ec) + throw_exception(system::system_error(ec, ei.message())); + return tmp; + } + ///@} + + ///@{ + /// Perform a query without parametert, It execute a multiple statement. + BOOST_SQLITE_DECL void execute( + cstring_ref q, + system::error_code & ec, + error_info & ei); + + BOOST_SQLITE_DECL void execute(cstring_ref q); + + template<typename StringLike, + typename = decltype(std::declval<const StringLike&>().c_str())> + void execute( + const StringLike& q, + system::error_code & ec, + error_info & ei) + { + execute(q.c_str(), ec, ei); + } + template<typename StringLike, + typename = decltype(std::declval<const StringLike&>().c_str())> + void execute(const StringLike & q) { execute(q.c_str());} + ///@} + + ///@{ + /// Perform a query with parameters. Can only execute a single statement. + BOOST_SQLITE_DECL statement prepare( + core::string_view q, + system::error_code & ec, + error_info & ei); + + BOOST_SQLITE_DECL statement prepare(core::string_view q); + ///@} + + /// Check if the database has the table + bool has_table( + cstring_ref table, + cstring_ref db_name = "main") const + { + return sqlite3_table_column_metadata(impl_.get(), db_name.c_str(), table.c_str(), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr) + == SQLITE_OK; + } + + /// Check if the database has the table + bool has_column( + cstring_ref table, + cstring_ref column, + cstring_ref db_name = "main") const + { + return sqlite3_table_column_metadata(impl_.get(), db_name.c_str(), table.c_str(), column.c_str(), + nullptr, nullptr, nullptr, nullptr, nullptr) + == SQLITE_OK; + } + private: + struct deleter_ + { + deleter_(bool owned = true) : owned_(owned) {} + bool owned_ = true; + void operator()(sqlite3 *impl) + { + if (owned_) + sqlite3_close_v2(impl); + } + }; + + std::unique_ptr<sqlite3, deleter_> impl_{nullptr, deleter_{}}; +}; + +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_CONNECTION_HPP diff --git a/include/boost/sqlite/cstring_ref.hpp b/include/boost/sqlite/cstring_ref.hpp new file mode 100644 index 0000000..6747f45 --- /dev/null +++ b/include/boost/sqlite/cstring_ref.hpp @@ -0,0 +1,223 @@ +// 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) +// based on boost.process +#ifndef BOOST_SQLITE_CSTRING_REF_HPP +#define BOOST_SQLITE_CSTRING_REF_HPP + +#include <boost/config.hpp> +#include <boost/core/detail/string_view.hpp> + +#include <cstddef> +#include <type_traits> +#include <string> + + +#if __cplusplus >= 201702L +#include <string_view> +#endif + +namespace boost +{ +namespace sqlite +{ + +/// Small wrapper for a null-terminated string that can be directly passed to C APIS +/** This ref can only be modified by moving the front pointer. It does not store the + * size, but can detect values that can directly be passed to system APIs. + * + * It can be constructed from a `char*` pointer or any class that has a `c_str()` + * member function, e.g. std::string or boost::static_string. + * + */ +struct cstring_ref +{ + using value_type = char; + using traits_type = std::char_traits<char>; + BOOST_CONSTEXPR cstring_ref() noexcept : view_("") {} + BOOST_CONSTEXPR cstring_ref(std::nullptr_t) = delete; + + BOOST_CONSTEXPR cstring_ref( const value_type* s ) : view_(s) {} + + template<typename Source, + typename = + typename std::enable_if< + std::is_same<const value_type, + typename std::remove_pointer<decltype(std::declval<Source>().c_str())>::type + >::value>::type> + BOOST_CONSTEXPR cstring_ref(Source && src) : view_(src.c_str()) {} + + BOOST_CONSTEXPR const char * c_str() const BOOST_NOEXCEPT + { + return this->data(); + } + + using string_view_type = core::string_view; + constexpr operator string_view_type() const {return view_;} + +#if __cplusplus >= 201702L + constexpr operator std::string_view() const {return view_;} +#endif + + using pointer = char *; + using const_pointer = const char *; + using reference = char &; + using const_reference = const char &; + using const_iterator = const_pointer; + using iterator = const_iterator; + using const_reverse_iterator = typename std::reverse_iterator<const_iterator>; + using reverse_iterator = typename std::reverse_iterator<iterator>; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + static BOOST_CONSTEXPR size_type npos = -1; + + BOOST_CONSTEXPR const_iterator begin() const BOOST_NOEXCEPT {return view_;}; + BOOST_CONSTEXPR const_iterator end() const BOOST_NOEXCEPT {return view_ + length();}; + BOOST_CONSTEXPR const_iterator cbegin() const BOOST_NOEXCEPT {return view_;}; + BOOST_CONSTEXPR const_iterator cend() const BOOST_NOEXCEPT {return view_ + length();}; + +#if defined(BOOST_NO_CXX17) + const_reverse_iterator rbegin() const BOOST_NOEXCEPT {return reverse_iterator(end());}; + const_reverse_iterator rend() const BOOST_NOEXCEPT {return reverse_iterator(begin());}; + const_reverse_iterator crbegin() const BOOST_NOEXCEPT {return reverse_iterator(end());}; + const_reverse_iterator crend() const BOOST_NOEXCEPT {return reverse_iterator(begin());}; +#else + BOOST_CONSTEXPR const_reverse_iterator rbegin() const BOOST_NOEXCEPT {return reverse_iterator(end());}; + BOOST_CONSTEXPR const_reverse_iterator rend() const BOOST_NOEXCEPT {return reverse_iterator(begin());}; + BOOST_CONSTEXPR const_reverse_iterator crbegin() const BOOST_NOEXCEPT {return reverse_iterator(end());}; + BOOST_CONSTEXPR const_reverse_iterator crend() const BOOST_NOEXCEPT {return reverse_iterator(begin());}; +#endif + + BOOST_CONSTEXPR size_type size() const BOOST_NOEXCEPT {return length(); } + BOOST_CONSTEXPR size_type length() const BOOST_NOEXCEPT {return length_impl_(); } + BOOST_CONSTEXPR size_type max_size() const BOOST_NOEXCEPT {return static_cast<std::size_t>(-1); } + BOOST_ATTRIBUTE_NODISCARD BOOST_CONSTEXPR bool empty() const BOOST_NOEXCEPT {return *view_ == '\0'; } + + BOOST_CONSTEXPR const_reference operator[](size_type pos) const {return view_[pos] ;} + BOOST_CXX14_CONSTEXPR const_reference at(size_type pos) const + { + if (pos >= size()) + throw_exception(std::out_of_range("cstring-view out of range")); + return view_[pos]; + } + BOOST_CONSTEXPR const_reference front() const {return *view_;} + BOOST_CONSTEXPR const_reference back() const {return view_[length() - 1];} + BOOST_CONSTEXPR const_pointer data() const BOOST_NOEXCEPT {return view_;} + BOOST_CXX14_CONSTEXPR void remove_prefix(size_type n) {view_ = view_ + n;} + void swap(cstring_ref& s) BOOST_NOEXCEPT {std::swap(view_, s.view_);} + + size_type copy(value_type* s, size_type n, size_type pos = 0) const + { + return traits_type::copy(s, view_ + pos, n) - view_; + } + BOOST_CONSTEXPR cstring_ref substr(size_type pos = 0) const + { + return cstring_ref(view_ + pos); + } + + BOOST_CXX14_CONSTEXPR string_view_type substr(size_type pos, size_type length) const + { + return string_view_type(view_).substr(pos, length); + } + + BOOST_CXX14_CONSTEXPR int compare(cstring_ref x) const BOOST_NOEXCEPT + { + auto idx = 0u; + for (; view_[idx] != null_char_()[0] && x[idx] != null_char_()[0]; idx++) + if (!traits_type::eq(view_[idx], x[idx])) + return traits_type::lt(view_[idx], x[idx]) ? -1 : 1; + + return traits_type::to_int_type(view_[idx]) - + traits_type::to_int_type(x[idx]); // will compare to null char of either. + } + + BOOST_CXX14_CONSTEXPR bool starts_with(string_view_type x) const BOOST_NOEXCEPT + { + if (x.empty()) + return true; + + auto idx = 0u; + for (; view_[idx] != null_char_()[0] && idx < x.size(); idx++) + if (!traits_type::eq(view_[idx], x[idx])) + return false; + + return idx == x.size() || view_[idx] != null_char_()[0]; + } + BOOST_CONSTEXPR bool starts_with(value_type x) const BOOST_NOEXCEPT + { + return traits_type::eq(view_[0], x); + } + + BOOST_CXX14_CONSTEXPR size_type find( char ch, size_type pos = 0 ) const BOOST_NOEXCEPT + { + for (auto p = view_ + pos; *p != *null_char_(); p++) + if (traits_type::eq(*p, ch)) + return p - view_; + return npos; + } + + + friend BOOST_CXX14_CONSTEXPR bool operator==(cstring_ref x, cstring_ref y) BOOST_NOEXCEPT + { + std::size_t idx = 0u; + for (idx = 0u; x[idx] != null_char_()[0] && y[idx] != null_char_()[0]; idx++) + if (!traits_type::eq(x[idx], y[idx])) + return false; + return x[idx] == y[idx]; + } + friend BOOST_CXX14_CONSTEXPR bool operator!=(cstring_ref x, cstring_ref y) BOOST_NOEXCEPT + { + std::size_t idx = 0u; + for (idx = 0u; x[idx] != null_char_()[0] && + y[idx] != null_char_()[0]; idx++) + if (!traits_type::eq(x[idx], y[idx])) + return true; + return x[idx] != y[idx]; + } + friend BOOST_CXX14_CONSTEXPR bool operator< (cstring_ref x, cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) < 0;} + friend BOOST_CXX14_CONSTEXPR bool operator> (cstring_ref x, cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) > 0;} + friend BOOST_CXX14_CONSTEXPR bool operator<=(cstring_ref x, cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) <= 0;} + friend BOOST_CXX14_CONSTEXPR bool operator>=(cstring_ref x, cstring_ref y) BOOST_NOEXCEPT {return x.compare(y) >= 0;} + + // modifiers + void clear() BOOST_NOEXCEPT { view_ = null_char_(); } // Boost extension + + std::basic_string<value_type, traits_type> to_string() const + { + return std::basic_string<char, traits_type>(begin(), end()); + } + + template<typename Allocator> + std::basic_string<value_type, traits_type, Allocator> to_string(const Allocator& a) const + { + return std::basic_string<value_type, traits_type, Allocator>(begin(), end(), a); + } + + template<class A> operator std::basic_string<char, std::char_traits<char>, A>() const + { + return std::basic_string<char, std::char_traits<char>, A>( view_ ); + } + + private: + BOOST_CONSTEXPR static const_pointer null_char_() {return "\0";} + constexpr std::size_t length_impl_(std::size_t n = 0) const BOOST_NOEXCEPT + { + return view_[n] == null_char_()[0] ? n : length_impl_(n+1); + } + const_pointer view_; +}; + +template<typename Char> +inline std::basic_ostream<Char>& operator<<( std::basic_ostream<Char> & os, cstring_ref str ) +{ + return os << core::string_view(str); +} + + +} +} + + +#endif //BOOST_SQLITE_CSTRING_REF_HPP diff --git a/include/boost/sqlite/detail/aggregate_function.hpp b/include/boost/sqlite/detail/aggregate_function.hpp new file mode 100644 index 0000000..de0c447 --- /dev/null +++ b/include/boost/sqlite/detail/aggregate_function.hpp @@ -0,0 +1,166 @@ +// Copyright (c) 2023 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_DETAIL_AGGREGATE_FUNCTION_HPP +#define BOOST_SQLITE_DETAIL_AGGREGATE_FUNCTION_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/detail/catch.hpp> +#include <boost/sqlite/error.hpp> +#include <boost/sqlite/cstring_ref.hpp> +#include <boost/sqlite/memory.hpp> +#include <boost/sqlite/result.hpp> +#include <boost/sqlite/value.hpp> + +#include <boost/callable_traits/args.hpp> +#include <boost/callable_traits/return_type.hpp> +#include <boost/callable_traits/has_void_return.hpp> +#include <boost/core/span.hpp> + + +BOOST_SQLITE_BEGIN_NAMESPACE + +namespace detail +{ + +template<typename Func> +struct aggregate_function_maker +{ + void * mem; + + template<typename ... Args> + Func* operator()(Args && ... args) + { + return new (mem) Func(std::forward<Args>(args)...); + } +}; + +template<typename Func, typename Args> +int create_aggregate_function(sqlite3 * db, cstring_ref name, Args && args, int flags, + std::true_type /* void return */) +{ + using args_type = callable_traits::args_t<decltype(&Func::step)>; + using span_type = typename std::tuple_element<1U, args_type>::type; + using func_type = typename std::decay<Func>::type; + using func_args_type = typename std::decay<Args>::type; + + return sqlite3_create_function_v2( + db, name.c_str(), + span_type::extent == boost::dynamic_extent ? -1 : static_cast<int>(span_type::extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_args_type(std::forward<Args>(args)), + nullptr, + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto aa = reinterpret_cast<value*>(args); + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result<void> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return error(SQLITE_NOMEM); + c = mp11::tuple_apply(aggregate_function_maker<func_type>{p}, *fa); + } + c->step(span_type{aa, static_cast<std::size_t>(len)}); + return {}; + }); + }, + [](sqlite3_context* ctx) + { + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result<decltype(c->final())> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return error(SQLITE_NOMEM); + c = mp11::tuple_apply(aggregate_function_maker<func_type>{p}, *fa); + } + struct reaper {void operator()(func_type * c) { c->~func_type();}}; + std::unique_ptr<func_type, reaper> cl{c}; + return c->final(); + }); + }, + [](void * ptr) noexcept { delete_(static_cast<func_type*>(ptr));} + ); +} + + +template<typename Func, typename Args> +int create_aggregate_function(sqlite3 * db, cstring_ref name, Args && args, int flags, + std::false_type /* void return */) +{ + using args_type = callable_traits::args_t<decltype(&Func::step)>; + using span_type = typename std::tuple_element<1U, args_type>::type; + using func_type = typename std::decay<Func>::type; + using func_args_type = typename std::decay<Args>::type; + + return sqlite3_create_function_v2( + db, name.c_str(), + span_type::extent == boost::dynamic_extent ? -1 : static_cast<int>(span_type::extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_args_type(std::forward<Args>(args)), + nullptr, + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto aa = reinterpret_cast<value*>(args); + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result<void> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return {system::in_place_error, error(SQLITE_NOMEM)}; + c = mp11::tuple_apply(aggregate_function_maker<func_type>{p}, *fa); + } + return c->step(span_type{aa, static_cast<std::size_t>(len)}); + }); + }, + [](sqlite3_context* ctx) + { + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result<decltype(c->final())> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return {system::in_place_error, error(SQLITE_NOMEM)}; + c = mp11::tuple_apply(aggregate_function_maker<func_type>{p}, *fa); + } + struct reaper {void operator()(func_type * c) { c->~func_type();}}; + std::unique_ptr<func_type, reaper> cl{c}; + return c->final(); + }); + }, + [](void * ptr) noexcept { delete_(static_cast<func_type*>(ptr));} + ); +} + +} + +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_DETAIL_AGGREGATE_FUNCTION_HPP diff --git a/include/boost/sqlite/detail/catch.hpp b/include/boost/sqlite/detail/catch.hpp new file mode 100644 index 0000000..e5b053f --- /dev/null +++ b/include/boost/sqlite/detail/catch.hpp @@ -0,0 +1,169 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_DETAIL_CATCH_HPP +#define BOOST_SQLITE_DETAIL_CATCH_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/detail/exception.hpp> +#include <boost/sqlite/error.hpp> +#include <boost/sqlite/result.hpp> + +#include <boost/system/system_error.hpp> + +#include <stdexcept> + + +BOOST_SQLITE_BEGIN_NAMESPACE +namespace detail +{ + +template<typename Func, typename ... Args> +void execute_context_function_impl(std::false_type /* is_void */, + sqlite3_context * ctx, + Func && func, Args && ... args) + noexcept(noexcept(std::forward<Func>(func)(std::forward<Args>(args)...))) +{ + set_result(ctx, std::forward<Func>(func)(std::forward<Args>(args)...)); +} + +template<typename Func, typename ... Args> +void execute_context_function_impl(std::true_type /* is_void */, + sqlite3_context * ctx, + Func && func, Args && ... args) + noexcept(noexcept(std::forward<Func>(func)(std::forward<Args>(args)...))) +{ + std::forward<Func>(func)(std::forward<Args>(args)...); +} + +template<typename T> +int extract_error(char * &errMsg, result<T> & res) +{ + error err = std::move(res).error(); + if (err.info) + errMsg = err.info.release(); + return err.code; +} + + +template<typename Func, typename ... Args> +void execute_context_function(sqlite3_context * ctx, + Func && func, Args && ... args) noexcept +{ + using return_type = decltype(func(std::forward<Args>(args)...)); +#if !defined(BOOST_NO_EXCEPTIONS) + try + { +#endif + execute_context_function_impl(std::is_void<return_type>{}, ctx, + std::forward<Func>(func), + std::forward<Args>(args)...); +#if !defined(BOOST_NO_EXCEPTIONS) + } + catch(boost::system::system_error & se) + { + const auto msg = boost::sqlite::detail::get_message(se); + if (!msg.empty()) + sqlite3_result_error(ctx, msg.data(), msg.size()); + if (se.code().category() == boost::sqlite::sqlite_category()) + sqlite3_result_error_code(ctx, se.code().value()); + } + catch(std::bad_alloc &) { sqlite3_result_error_nomem(ctx); } + catch(std::length_error &) { sqlite3_result_error_toobig(ctx); } + catch(std::out_of_range &) { sqlite3_result_error_code(ctx, SQLITE_RANGE);} + catch(std::logic_error &le) + { + sqlite3_result_error(ctx, le.what(), std::strlen(le.what())); + sqlite3_result_error_code(ctx, SQLITE_MISUSE); + } + catch(std::exception & ex) + { + sqlite3_result_error(ctx, ex.what(), std::strlen(ex.what())); + } + catch(...) {sqlite3_result_error_code(ctx, SQLITE_ERROR); } +#endif +} + +} +BOOST_SQLITE_END_NAMESPACE + +#if defined(BOOST_NO_EXCEPTIONS) +#define BOOST_SQLITE_TRY +#define BOOST_SQLITE_CATCH_RESULT(ctx) +#define BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(msg) +#define BOOST_SQLITE_CATCH_AND_RETURN() + +#else + +#define BOOST_SQLITE_TRY try +#define BOOST_SQLITE_CATCH_RESULT(ctx) \ +catch(boost::system::system_error & se) \ +{ \ + if (se.code().category() == boost::sqlite::sqlite_category()) \ + sqlite3_result_error_code(ctx, se.code().value()); \ + const auto msg = boost::sqlite::detail::get_message(se); \ + if (!msg.empty()) \ + sqlite3_result_error(ctx, msg.data(), msg.size()); \ +} \ +catch(std::bad_alloc &) { sqlite3_result_error_nomem(ctx); } \ +catch(std::length_error &) { sqlite3_result_error_toobig(ctx); } \ +catch(std::out_of_range &) { sqlite3_result_error_code(ctx, SQLITE_RANGE);} \ +catch(std::logic_error &le) \ +{ \ + sqlite3_result_error(ctx, le.what(), std::strlen(le.what())); \ + sqlite3_result_error_code(ctx, SQLITE_MISUSE); \ +} \ +catch(std::exception & ex) \ +{ \ + sqlite3_result_error(ctx, ex.what(), std::strlen(ex.what())); \ +} \ +catch(...) {sqlite3_result_error_code(ctx, SQLITE_ERROR); } + + +#define BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(msg) \ +catch (boost::system::system_error & se) \ +{ \ + auto code = SQLITE_ERROR; \ + if (se.code().category() == boost::sqlite::sqlite_category()) \ + code = se.code().value(); \ + const auto pre = boost::sqlite::detail::get_message(se); \ + msg = boost::sqlite::error_info(pre).release(); \ + return code; \ +} \ +catch(std::bad_alloc &) { return SQLITE_NOMEM; } \ +catch(std::length_error &) { return SQLITE_TOOBIG; } \ +catch(std::out_of_range &) { return SQLITE_RANGE;} \ +catch(std::logic_error &le) \ +{ \ + msg = boost::sqlite::error_info(le.what()).release(); \ + return SQLITE_MISUSE; \ +} \ +catch(std::exception & ex) \ +{ \ + msg = boost::sqlite::error_info(ex.what()).release(); \ + return SQLITE_ERROR; \ +} \ +catch(...) {return SQLITE_ERROR; } \ + + +#define BOOST_SQLITE_CATCH_AND_RETURN() \ +catch (boost::system::system_error & se) \ +{ \ + auto code = SQLITE_ERROR; \ + if (se.code().category() == boost::sqlite::sqlite_category()) \ + code = se.code().value(); \ + return code; \ +} \ +catch(std::bad_alloc &) { return SQLITE_NOMEM; } \ +catch(std::length_error &) { return SQLITE_TOOBIG; } \ +catch(std::out_of_range &) { return SQLITE_RANGE;} \ +catch(std::logic_error &) { return SQLITE_MISUSE;} \ +catch(...) { return SQLITE_ERROR; } \ + +#endif + +#endif //BOOST_SQLITE_DETAIL_CATCH_HPP diff --git a/include/boost/sqlite/detail/config.hpp b/include/boost/sqlite/detail/config.hpp new file mode 100644 index 0000000..2126f60 --- /dev/null +++ b/include/boost/sqlite/detail/config.hpp @@ -0,0 +1,83 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_DETAIL_CONFIG_HPP +#define BOOST_SQLITE_DETAIL_CONFIG_HPP + +#include <boost/config.hpp> +#include <boost/core/detail/string_view.hpp> + +#if defined(BOOST_SQLITE_COMPILE_EXTENSION) +#include <sqlite3ext.h> +#define BOOST_SQLITE_COMPILING_EXTENSION 1 +#define BOOST_SQLITE_BEGIN_NAMESPACE namespace boost { namespace sqlite { inline namespace ext { +#define BOOST_SQLITE_END_NAMESPACE } } } +#else +#include <sqlite3.h> +#define BOOST_SQLITE_BEGIN_NAMESPACE namespace boost { namespace sqlite { +#define BOOST_SQLITE_END_NAMESPACE } } +#endif + +// copied from boost.json +#if defined(BOOST_SQLITE_DOCS) +# define BOOST_SQLITE_DECL +#else +# if (defined(BOOST_SQLITE_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && !defined(BOOST_SQLITE_STATIC_LINK) +# if defined(BOOST_SQLITE_SOURCE) +# define BOOST_SQLITE_DECL BOOST_SYMBOL_EXPORT +# else +# define BOOST_SQLITE_DECL BOOST_SYMBOL_IMPORT +# endif +# endif // static lib +# ifndef BOOST_SQLITE_DECL +# define BOOST_SQLITE_DECL +# endif +# if !defined(BOOST_SQLITE_SOURCE) && !defined(BOOST_ALL_NO_LIB) && !defined(BOOST_SQLITE_NO_LIB) +# define BOOST_LIB_NAME boost_sqlite +# if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_SQLITE_DYN_LINK) +# define BOOST_DYN_LINK +# endif +# include <boost/config/auto_link.hpp> +# endif +#endif + +BOOST_SQLITE_BEGIN_NAMESPACE + + +#if defined(BOOST_SQLITE_COMPILE_EXTENSION) +extern const sqlite3_api_routines *sqlite3_api; +#endif + +using string_view = boost::core::string_view; + +BOOST_SQLITE_END_NAMESPACE + +#define BOOST_SQLITE_RETURN_EC(ev) \ +do \ +{ \ + static constexpr auto loc##__LINE__((BOOST_CURRENT_LOCATION)); \ + return ::boost::system::error_code(ev, boost::sqlite::sqlite_category(), &loc##__LINE__); \ +} \ +while (false) + +#define BOOST_SQLITE_ASSIGN_EC(ec, ev) \ +do \ +{ \ + static constexpr auto loc##__LINE__((BOOST_CURRENT_LOCATION)); \ + ec.assign(ev, boost::sqlite::sqlite_category(), &loc##__LINE__); \ +} \ +while (false) + +#if defined(BOOST_SQLITE_NO_VIRTUAL) +#define BOOST_SQLITE_VIRTUAL +#define BOOST_SQLITE_PURE +#else +#define BOOST_SQLITE_VIRTUAL virtual +#define BOOST_SQLITE_PURE = 0 +#endif + +#endif // BOOST_SQLITE_DETAIL_HPP
\ No newline at end of file diff --git a/include/boost/sqlite/detail/exception.hpp b/include/boost/sqlite/detail/exception.hpp new file mode 100644 index 0000000..9f29cbe --- /dev/null +++ b/include/boost/sqlite/detail/exception.hpp @@ -0,0 +1,48 @@ +// Copyright (c) 2023 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_DETAIL_EXCEPTION_HPP +#define BOOST_SQLITE_DETAIL_EXCEPTION_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/error.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE +namespace detail +{ + +BOOST_SQLITE_DECL +BOOST_NORETURN void throw_error_code(const boost::system::error_code & ec, + const boost::source_location & loc = BOOST_CURRENT_LOCATION); + +BOOST_SQLITE_DECL +BOOST_NORETURN void throw_error_code(const boost::system::error_code & ec, + const error_info & ei, + const boost::source_location & loc = BOOST_CURRENT_LOCATION); + +BOOST_SQLITE_DECL +BOOST_NORETURN void throw_out_of_range(const char * msg, + const boost::source_location & loc); + +BOOST_SQLITE_DECL +BOOST_NORETURN void throw_invalid_argument(const char * msg, + const boost::source_location & loc); + +inline core::string_view get_message(const system::system_error & se) +{ + auto ec_len = se.code().what().size(); + auto se_len = std::strlen(se.what()); + + if (ec_len == se_len) + return core::string_view(); + else + return core::string_view(se.what(), se_len - (ec_len + 2)); +} + + +} +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_DETAIL_EXCEPTION_HPP diff --git a/include/boost/sqlite/detail/scalar_function.hpp b/include/boost/sqlite/detail/scalar_function.hpp new file mode 100644 index 0000000..ee16a23 --- /dev/null +++ b/include/boost/sqlite/detail/scalar_function.hpp @@ -0,0 +1,172 @@ +// Copyright (c) 2023 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_DETAIL_SCALAR_FUNCTION_HPP +#define BOOST_SQLITE_DETAIL_SCALAR_FUNCTION_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/cstring_ref.hpp> +#include <boost/sqlite/memory.hpp> +#include <boost/sqlite/result.hpp> +#include <boost/sqlite/value.hpp> + +#include <boost/callable_traits/args.hpp> +#include <boost/callable_traits/return_type.hpp> +#include <boost/callable_traits/has_void_return.hpp> +#include <boost/core/span.hpp> + + +BOOST_SQLITE_BEGIN_NAMESPACE + +template<typename ... Args> +struct context; + +namespace detail +{ + +template<typename Func, typename ... Args, std::size_t Extent> +auto create_scalar_function_impl(sqlite3 * db, + cstring_ref name, + Func && func, + int flags, + std::tuple<context<Args...>, boost::span<value, Extent>> * , + std::false_type /* void return */, + std::false_type /* is pointer */) -> int +{ + using func_type = typename std::decay<Func>::type; + return sqlite3_create_function_v2( + db, name.c_str(), + Extent == boost::dynamic_extent ? -1 : static_cast<int>(Extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_type(std::forward<Func>(func)), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context<Args...>(ctx); + auto aa = reinterpret_cast<value*>(args); + auto &f = *reinterpret_cast<func_type*>(sqlite3_user_data(ctx)); + boost::span<value, Extent> vals{aa, static_cast<std::size_t>(len)}; + + execute_context_function(ctx, f, cc, vals); + }, nullptr, nullptr, + +[](void * ptr) noexcept {delete_(static_cast<func_type*>(ptr));} + ); +} + +template<typename Func, typename ... Args, std::size_t Extent> +auto create_scalar_function_impl(sqlite3 * db, + cstring_ref name, + Func && func, int flags, + std::tuple<context<Args...>, boost::span<value, Extent>> * , + std::true_type /* void return */, + std::false_type /* is pointer */) -> int +{ + using func_type = typename std::decay<Func>::type; + return sqlite3_create_function_v2( + db, + name.c_str(), + (Extent == boost::dynamic_extent) ? -1 : static_cast<int>(Extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_type(std::forward<Func>(func)), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context<Args...>(ctx); + auto aa = reinterpret_cast<value*>(args); + auto &f = *reinterpret_cast<func_type*>(sqlite3_user_data(ctx)); + boost::span<value, Extent> vals{aa, static_cast<std::size_t>(len)}; + + execute_context_function( + ctx, + [&]() + { + f(cc, vals); + return result<void>(); + }); + + }, nullptr, nullptr, + +[](void * ptr){delete_(static_cast<func_type*>(ptr));} + ); +} + + +template<typename Func, typename ... Args, std::size_t Extent> +auto create_scalar_function_impl(sqlite3 * db, + cstring_ref name, + Func * func, int flags, + std::tuple<context<Args...>, boost::span<value, Extent>> * , + std::false_type /* void return */, + std::true_type /* is pointer */) -> int +{ + return sqlite3_create_function_v2( + db, name.c_str(), + Extent == boost::dynamic_extent ? -1 : static_cast<int>(Extent), + SQLITE_UTF8 | flags, + reinterpret_cast<void*>(func), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context<Args...>(ctx); + auto aa = reinterpret_cast<value*>(args); + auto f = reinterpret_cast<Func*>(sqlite3_user_data(ctx)); + + boost::span<value, Extent> vals{aa, static_cast<std::size_t>(len)}; + + execute_context_function( + ctx, f, cc, vals); + }, nullptr, nullptr, nullptr); +} + +template<typename Func, typename ... Args, std::size_t Extent> +auto create_scalar_function_impl(sqlite3 * db, + cstring_ref name, + Func * func, int flags, + std::tuple<context<Args...>, boost::span<value, Extent>> * , + std::true_type /* void return */, + std::true_type /* is pointer */) -> int +{ + return sqlite3_create_function_v2( + db, + name.c_str(), + (Extent == boost::dynamic_extent) ? -1 : static_cast<int>(Extent), + SQLITE_UTF8 | flags, + reinterpret_cast<void*>(func), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context<Args...>(ctx); + auto aa = reinterpret_cast<value*>(args); + auto f = *reinterpret_cast<Func*>(sqlite3_user_data(ctx)); + boost::span<value, Extent> vals{aa, static_cast<std::size_t>(len)}; + execute_context_function( + ctx, + [&]() + { + f(cc, vals); + return result<void>(); + }); + + }, nullptr, nullptr, nullptr); +} + +template<typename Func> +auto create_scalar_function(sqlite3 * db, + cstring_ref name, + Func && func, + int flags) + -> decltype(create_scalar_function_impl( + db, name, std::forward<Func>(func), flags, + static_cast<callable_traits::args_t<Func>*>(nullptr), + callable_traits::has_void_return<Func>{}, + std::is_pointer<typename std::decay<Func>::type>{} + )) +{ + return create_scalar_function_impl(db, name, std::forward<Func>(func), flags, + static_cast<callable_traits::args_t<Func>*>(nullptr), + callable_traits::has_void_return<Func>{}, + std::is_pointer<typename std::decay<Func>::type>{}); +} + + +} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_DETAIL_SCALAR_FUNCTION_HPP diff --git a/include/boost/sqlite/detail/vtable.hpp b/include/boost/sqlite/detail/vtable.hpp new file mode 100644 index 0000000..f85f44c --- /dev/null +++ b/include/boost/sqlite/detail/vtable.hpp @@ -0,0 +1,611 @@ +// Copyright (c) 2023 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_DETAIL_VTABLE_HPP +#define BOOST_SQLITE_DETAIL_VTABLE_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/detail/catch.hpp> +#include <boost/sqlite/vtable.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE +namespace detail +{ +struct vtab_impl +{ + +template<typename Module> +static int connect(sqlite3 * db, void * pAux, int argc, const char * const * argv, + sqlite3_vtab **ppVTab, char** errMsg) +{ + using table_type = typename Module::table_type; + auto &impl = *static_cast<Module*>(pAux); + BOOST_SQLITE_TRY + { + result<table_type> rtab = impl.connect( + sqlite::connection(db, false), + argc, argv); + + if (rtab.has_error()) + return extract_error(*errMsg, rtab); + + auto tab = make_unique<table_type>(std::move(*rtab)); + tab->db_ = db; + auto code = sqlite3_declare_vtab(db, tab->declaration()); + if (code != SQLITE_OK) + return code; + + sqlite::vtab::module_config cfg{db}; + auto r = tab->config(cfg); + if (r.has_error()) + return extract_error(*errMsg, r); + + *ppVTab = tab.release(); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(*errMsg) +} + + +template<typename Module> +static int create(sqlite3 * db, void * pAux, int argc, const char * const * argv, + sqlite3_vtab **ppVTab, char** errMsg) +{ + using table_type = typename Module::table_type; + auto &impl = *static_cast<Module*>(pAux); + BOOST_SQLITE_TRY + { + result<table_type> rtab = impl.create( + sqlite::connection(db, false), + argc, argv); + + if (rtab.has_error()) + return extract_error(*errMsg, rtab); + + auto tab = make_unique<table_type>(std::move(*rtab)); + tab->db_ = db; + + auto code = sqlite3_declare_vtab(db, tab->declaration()); + if (code != SQLITE_OK) + return code; + + sqlite::vtab::module_config mc{db}; + auto r = tab->config(mc); + if (r.has_error()) + return extract_error(*errMsg, r); + + *ppVTab = tab.release(); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(*errMsg) +} + +template<typename Table> +static int disconnect(sqlite3_vtab * tab) +{ + BOOST_SQLITE_TRY + { + auto tb = static_cast<Table*>(tab); + tb->~Table(); + sqlite3_free(tb); + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(tab->zErrMsg); +} + +template<typename Table> +static int destroy(sqlite3_vtab * tab) +{ + BOOST_SQLITE_TRY + { + auto tb = static_cast<Table*>(tab); + auto res = tb->destroy(); + tb->~Table(); + sqlite3_free(tb); + if (res.has_error()) + return std::move(res).error().code; + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(tab->zErrMsg); +} + +template<typename Module, typename Table> +static void assign_create(sqlite3_module & md, const Module &, + const sqlite::vtab::eponymous_module<Table> & base) +{ + md.xConnect = md.xCreate = &connect<Module>; + md.xDisconnect = md.xDestroy = &disconnect<Table>; + if (base.eponymous_only()) + md.xCreate = nullptr; +} + +template<typename Module, typename Table> +static void assign_create(sqlite3_module & md, const Module &, + const sqlite::vtab::module<Table> &) +{ + md.xConnect = &connect<Module>; + md.xDisconnect = &disconnect<Table>; + md.xCreate = &create<Module>; + md.xDestroy = &destroy<Table>; +} + +template<typename Table> +static int open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) +{ + auto tab = static_cast<Table *>(pVTab); + + BOOST_SQLITE_TRY + { + auto res = tab->open(); + if (res.has_error()) + return extract_error(pVTab->zErrMsg, res); + *ppCursor = new (memory_tag{}) typename Table::cursor_type(std::move(*res)); + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_AND_RETURN(); +} + +template<typename Cursor> +static int close(sqlite3_vtab_cursor * cursor) +{ + auto p = static_cast<Cursor *>(cursor); + + BOOST_SQLITE_TRY + { + p->~Cursor(); + } + BOOST_SQLITE_CATCH_AND_RETURN(); + + sqlite3_free(p); + return SQLITE_OK; + +} + +template<typename Table> +static int best_index(sqlite3_vtab *pVTab, sqlite3_index_info* info) +{ + BOOST_SQLITE_TRY + { + auto tb = static_cast<Table*>(pVTab); + + sqlite::vtab::index_info ii(tb->db_, info); + auto r = tb->best_index(ii); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); +} + + +template<typename Cursor> +static int filter(sqlite3_vtab_cursor* pCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Cursor*>(pCursor); + + auto r = cr->filter(idxNum, idxStr, + boost::span<value>{reinterpret_cast<value*>(argv), + static_cast<std::size_t>(argc)}); + if (r.has_error()) + return extract_error(pCursor->pVtab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pCursor->pVtab->zErrMsg); +} + + +template<typename Cursor> +static int next(sqlite3_vtab_cursor* pCursor) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Cursor*>(pCursor); + + auto r = cr->next(); + if (r.has_error()) + return extract_error(pCursor->pVtab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pCursor->pVtab->zErrMsg); +} + + +template<typename Cursor> +static int eof(sqlite3_vtab_cursor* pCursor) +{ + return static_cast<Cursor*>(pCursor)->eof() ? 1 : 0; +} + +template<typename Cursor> +static auto column(sqlite3_vtab_cursor* pCursor, + sqlite3_context * ctx, int idx) + -> typename std::enable_if<!std::is_void<typename Cursor::column_type>::value, int>::type +{ +#if SQLITE_VERSION_NUMBER >= 3032000 + bool no_change = sqlite3_vtab_nochange(ctx) != 0; +#else + bool no_change = false; +#endif + auto cr = static_cast<Cursor*>(pCursor); + execute_context_function( + ctx, + [&]{ + return cr->column(idx, no_change); + }); + + return SQLITE_OK; +} + + +template<typename Cursor> +static auto column(sqlite3_vtab_cursor* pCursor, + sqlite3_context * ctx, int idx) + -> typename std::enable_if<std::is_void<typename Cursor::column_type>::value, int>::type +{ +#if SQLITE_VERSION_NUMBER >= 3032000 + bool no_change = sqlite3_vtab_nochange(ctx) != 0; +#else + bool no_change = false; +#endif + auto cr = static_cast<Cursor*>(pCursor); + BOOST_SQLITE_TRY + { + cr->column(context<>{ctx}, idx, no_change); + } + BOOST_SQLITE_CATCH_RESULT(ctx); + return SQLITE_OK; +} + +template<typename Cursor> +static int row_id(sqlite3_vtab_cursor* pCursor, sqlite3_int64 *pRowid) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Cursor*>(pCursor); + + auto r = cr->row_id(); + if (r.has_error()) + return extract_error(pCursor->pVtab->zErrMsg, r); + *pRowid = *r; + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pCursor->pVtab->zErrMsg); +} + +template<typename Table> +static int update(sqlite3_vtab * pVTab, int argc, sqlite3_value ** argv, sqlite3_int64 * pRowid) +{ + using table_type = Table; + BOOST_SQLITE_TRY + { + auto & mod = *static_cast<table_type *>(pVTab); + auto db = mod.db_; + if (argc == 1 && sqlite3_value_type(argv[0]) != SQLITE_NULL) + { + auto r = mod.delete_(sqlite::value(*argv)); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + } + else if (argc > 1 && sqlite3_value_type(argv[0]) == SQLITE_NULL) + { + auto r = mod.insert(value{argv[1]}, + boost::span<value>{reinterpret_cast<value *>(argv + 2), + static_cast<std::size_t>(argc - 2)}, + sqlite3_vtab_on_conflict(db)); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + *pRowid = r.value(); + } + else if (argc > 1 && sqlite3_value_type(argv[0]) != SQLITE_NULL) + { + auto r = mod.update(sqlite::value(*argv), value{argv[1]}, // ID + boost::span<value>{reinterpret_cast<value *>(argv + 2), + static_cast<std::size_t>(argc - 2)}, + sqlite3_vtab_on_conflict(db)); + + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + *pRowid = r.value(); + } + else + { + pVTab->zErrMsg = sqlite3_mprintf("Misuse of update api"); + return SQLITE_MISUSE; + } + + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg) + return SQLITE_OK; +} + +template<typename Module> +static void assign_update(sqlite3_module & md, const Module &, + std::true_type /* modifiable */) +{ + md.xUpdate = &update<typename Module::table_type>; +} + +template<typename Module> +static void assign_update(sqlite3_module & /*md*/, const Module &, + std::false_type /* modifiable */) +{ +} + +template<typename Table> +static int begin(sqlite3_vtab* pVTab) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Table*>(pVTab); + + auto r = cr->begin(); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); +} + +template<typename Table> +static int sync(sqlite3_vtab* pVTab) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Table*>(pVTab); + + auto r = cr->sync(); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); +} + +template<typename Table> +static int commit(sqlite3_vtab* pVTab) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Table*>(pVTab); + + auto r = cr->commit(); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); +} + +template<typename Table> +static int rollback(sqlite3_vtab* pVTab) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Table*>(pVTab); + + auto r = cr->rollback(); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); +} + +template<typename Module> +static void assign_transaction(sqlite3_module & /*md*/, const Module &, + std::false_type /* modifiable */) +{ +} + +template<typename Module> +static void assign_transaction(sqlite3_module & md, const Module &, + std::true_type /* modifiable */) +{ + md.xBegin = &begin <typename Module::table_type>; + md.xSync = &sync <typename Module::table_type>; + md.xCommit = &commit <typename Module::table_type>; + md.xRollback = &rollback<typename Module::table_type>; +} + +template<typename Table> +static int find_function(sqlite3_vtab *pVtab, int nArg, const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Table*>(pVtab); + + auto r = cr->find_function( + nArg, zName, sqlite::vtab::function_setter(pxFunc, ppArg)); + if (r.has_error()) + return extract_error(pVtab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVtab->zErrMsg); +} + +template<typename Module> +static void assign_find_function(sqlite3_module & /*md*/, const Module &, + std::false_type /* overloadable */) +{ +} + +template<typename Module> +static void assign_find_function(sqlite3_module & md, const Module &, + std::true_type /* overloadable */) +{ + md.xFindFunction = &find_function<typename Module::table_type>; +} + +template<typename Table> +static int rename(sqlite3_vtab* pVTab, const char * name) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Table*>(pVTab); + + auto r = cr->rename(name); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); +} + +template<typename Module> +static void assign_rename(sqlite3_module & /*md*/, const Module &, + std::false_type /* renamable */) +{ +} + +template<typename Module> +static void assign_rename(sqlite3_module & md, const Module &, + std::true_type /* renamable */) +{ + md.xRename = &rename<typename Module::table_type>; +} +#if SQLITE_VERSION_NUMBER >= 3007007 + +template<typename Table> +static int savepoint(sqlite3_vtab* pVTab, int i) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Table*>(pVTab); + + auto r = cr->savepoint(i); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); +} + +template<typename Table> +static int release(sqlite3_vtab* pVTab, int i) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Table*>(pVTab); + + auto r = cr->release(i); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); +} + +template<typename Table> +static int rollback_to(sqlite3_vtab* pVTab, int i) +{ + BOOST_SQLITE_TRY + { + auto cr = static_cast<Table*>(pVTab); + + auto r = cr->rollback_to(i); + if (r.has_error()) + return extract_error(pVTab->zErrMsg, r); + + return SQLITE_OK; + } + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); +} + +template<typename Module> +static void assign_recursive_transaction(sqlite3_module & /*md*/, const Module &, + std::false_type /* recursive_transaction */) +{ +} + +template<typename Module> +static void assign_recursive_transaction(sqlite3_module & md, const Module &, + std::true_type /* recursive_transaction */) +{ + md.xSavepoint = &savepoint <typename Module::table_type>; + md.xRelease = &release <typename Module::table_type>; + md.xRollbackTo = &rollback_to<typename Module::table_type>; +} + +#endif + +#if SQLITE_VERSION_NUMBER >= 3026000 + +template<typename Table> +static void assign_shadow_name(sqlite3_module & /*md*/, const sqlite::vtab::module<Table> &) {} + +template<typename Table> +static void assign_shadow_name(sqlite3_module & /*md*/, const sqlite::vtab::eponymous_module<Table> &) {} + +template<typename Module, + bool (*Func)(const char *) = &Module::shadow_name> +static void assign_shadow_name(sqlite3_module & md, const Module & mod) +{ + md.xShadowName = +[](const char * name){return Func(name) != 0;}; +} + +#endif + +}; + +template<typename Module> +const sqlite3_module make_module(const Module & mod) +{ + sqlite3_module md; + std::memset(&md, 0, sizeof(sqlite3_module)); + +#if SQLITE_VERSION_NUMBER < 3007007 + md.iVersion = 1; +#elif SQLITE_VERSION_NUMBER < 3026000 + md.iVersion = 2; +#else + md.iVersion = 3; +#endif + using table_type = typename Module::table_type; + using cursor_type = typename table_type::cursor_type; + vtab_impl::assign_create(md, mod, mod); + md.xBestIndex = &vtab_impl::best_index<table_type>; + md.xOpen = &vtab_impl::open <table_type>; + md.xClose = &vtab_impl::close <cursor_type>; + md.xFilter = &vtab_impl::filter <cursor_type>; + md.xNext = &vtab_impl::next <cursor_type>; + md.xEof = &vtab_impl::eof <cursor_type>; + md.xColumn = &vtab_impl::column <cursor_type>; + md.xRowid = &vtab_impl::row_id <cursor_type>; + vtab_impl::assign_update (md, mod, std::is_base_of<sqlite::vtab::modifiable, table_type>{}); + vtab_impl::assign_transaction (md, mod, std::is_base_of<sqlite::vtab::transaction, table_type>{}); + vtab_impl::assign_find_function(md, mod, std::is_base_of<sqlite::vtab::overload_functions, table_type>{}); + vtab_impl::assign_rename (md, mod, std::is_base_of<sqlite::vtab::renamable, table_type>{}); +#if SQLITE_VERSION_NUMBER >= 3007007 + vtab_impl::assign_recursive_transaction(md, mod, std::is_base_of<sqlite::vtab::recursive_transaction, table_type>{}); +#endif +#if SQLITE_VERSION_NUMBER >= 3026000 + vtab_impl::assign_shadow_name(md, mod); +#endif + return md; +} + +} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_DETAIL_VTABLE_HPP diff --git a/include/boost/sqlite/detail/window_function.hpp b/include/boost/sqlite/detail/window_function.hpp new file mode 100644 index 0000000..df8475c --- /dev/null +++ b/include/boost/sqlite/detail/window_function.hpp @@ -0,0 +1,238 @@ +// Copyright (c) 2023 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_IMPL_WINDOW_FUNCTION_HPP +#define BOOST_SQLITE_IMPL_WINDOW_FUNCTION_HPP +#if SQLITE_VERSION_NUMBER >= 3025000 + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/cstring_ref.hpp> +#include <boost/sqlite/memory.hpp> +#include <boost/sqlite/result.hpp> +#include <boost/sqlite/value.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + +namespace detail +{ + +template<typename Func> +struct window_function_maker +{ + void * mem; + + template<typename ... Args> + Func* operator()(Args && ... args) + { + return new (mem) Func(std::forward<Args>(args)...); + } +}; + +template<typename Func, typename Args> +int create_window_function(sqlite3 * db, cstring_ref name, Args && args, int flags, + std::true_type /* is void */) +{ + using args_type = callable_traits::args_t<decltype(&Func::step)>; + using span_type = typename std::tuple_element<1U, args_type>::type; + using func_type = typename std::decay<Func>::type; + using func_args_type = typename std::decay<Args>::type; + + return sqlite3_create_window_function( + db, name.c_str(), + span_type::extent == boost::dynamic_extent ? -1 : static_cast<int>(span_type::extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_args_type(std::forward<Args>(args)), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept //xStep + { + auto aa = reinterpret_cast<value*>(args); + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result<void> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return error(SQLITE_NOMEM); + c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa); + } + c->step(span_type{aa, static_cast<std::size_t>(len)}); + return {}; + }); + + }, + [](sqlite3_context* ctx) // xFinal + { + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result<decltype(c->value())> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return error(SQLITE_NOMEM); + c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa); + } + struct reaper {void operator()(func_type * c) { c->~func_type();}}; + std::unique_ptr<func_type, reaper> cl{c}; + + return c->value(); + }); + }, + [](sqlite3_context* ctx) //xValue + { + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> result<decltype(c->value())> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return error(SQLITE_NOMEM); + c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa); + } + return c->value(); + }); + + }, + +[](sqlite3_context* ctx, int len, sqlite3_value** args) // xInverse + { + auto aa = reinterpret_cast<value*>(args); + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> result<decltype(c->inverse(span_type{aa, static_cast<std::size_t>(len)}))> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return error(SQLITE_NOMEM); + c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa); + } + c->inverse(span_type{aa, static_cast<std::size_t>(len)}); + return {}; + }); + + }, + [](void * ptr) /* xDestroy */ { delete_(static_cast<func_type*>(ptr));} + ); +} + +template<typename Func, typename Args> +int create_window_function(sqlite3 * db, cstring_ref name, Args && args, int flags, + std::false_type /* is void */) +{ + using args_type = callable_traits::args_t<decltype(&Func::step)>; + using span_type = typename std::tuple_element<1U, args_type>::type; + using func_type = typename std::decay<Func>::type; + using func_args_type = typename std::decay<Args>::type; + + return sqlite3_create_window_function( + db, name.c_str(), + span_type::extent == boost::dynamic_extent ? -1 : static_cast<int>(span_type::extent), + SQLITE_UTF8 | flags, + new (memory_tag{}) func_args_type(std::forward<Args>(args)), + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept //xStep + { + auto aa = reinterpret_cast<value*>(args); + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + + execute_context_function( + ctx, + [&]() -> result<decltype(c->step(span_type{aa, static_cast<std::size_t>(len)}))> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return {system::in_place_error, error(SQLITE_NOMEM)}; + c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa); + } + return c->step(span_type{aa, static_cast<std::size_t>(len)}); + }); + + }, + [](sqlite3_context* ctx) // xFinal + { + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> result<decltype(c->value())> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return {system::in_place_error, error(SQLITE_NOMEM)}; + c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa); + } + + struct reaper {void operator()(func_type * c) { c->~func_type();}}; + std::unique_ptr<func_type, reaper> cl{c}; + return c->value(); + }); + }, + [](sqlite3_context* ctx) //xValue + { + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> result<decltype(c->value())> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return {system::in_place_error, error(SQLITE_NOMEM)}; + c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa); + } + return c->value(); + }); + + }, + +[](sqlite3_context* ctx, int len, sqlite3_value** args) // xInverse + { + auto aa = reinterpret_cast<value*>(args); + auto fa = reinterpret_cast<func_args_type*>(sqlite3_user_data(ctx)); + auto c = static_cast<func_type*>(sqlite3_aggregate_context(ctx, 0)); + execute_context_function( + ctx, + [&]() -> result<decltype(c->inverse(span_type{aa, static_cast<std::size_t>(len)}))> + { + if (c == nullptr) + { + auto p = sqlite3_aggregate_context(ctx, sizeof(func_type)); + if (!p) + return {system::in_place_error, error(SQLITE_NOMEM)}; + c = mp11::tuple_apply(window_function_maker<func_type>{p}, *fa); + } + return c->inverse(span_type{aa, static_cast<std::size_t>(len)}); + }); + + }, + [](void * ptr) /* xDestroy */ { delete_(static_cast<func_type*>(ptr));} + ); +} + + +} + +BOOST_SQLITE_END_NAMESPACE +#endif // SQLITE_VERSION +#endif //BOOST_SQLITE_IMPL_WINDOW_FUNCTION_HPP diff --git a/include/boost/sqlite/error.hpp b/include/boost/sqlite/error.hpp new file mode 100644 index 0000000..9fe6211 --- /dev/null +++ b/include/boost/sqlite/error.hpp @@ -0,0 +1,178 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_ERROR_HPP +#define BOOST_SQLITE_ERROR_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/cstring_ref.hpp> +#include <boost/sqlite/memory.hpp> +#include <boost/system/error_code.hpp> +#include <boost/system/error_category.hpp> +#include <boost/system/result.hpp> + + +BOOST_SQLITE_BEGIN_NAMESPACE + +BOOST_SQLITE_DECL +system::error_category & sqlite_category(); + + +/** + * \brief Additional information about error conditions stored in an sqlite-allocate string + * \ingroup reference + * \details Contains an error message describing what happened. Not all error + * conditions are able to generate this extended information - those that + * can't have an empty error message. + */ +struct error_info +{ + /// Default constructor. + error_info() = default; + + /// Initialization constructor. + error_info(core::string_view msg) noexcept : msg_(new (memory_tag{}) char[msg.size() + 1u]) + { + std::memcpy(msg_.get(), msg.data(), (std::min)(msg.size() + 1, capacity())); + } + + /// set the message by copy + void set_message(core::string_view msg) + { + reserve(msg.size() + 1u); + std::memcpy(msg_.get(), msg.data(), (std::min)(msg.size() + 1, capacity())); + } + /// transfer ownership into the message + void reset(char * c = nullptr) + { + msg_.reset(c); + } + + /// use sqlite_mprintf to generate a message +#if defined(__GNUC__) + cstring_ref format(const char * fmt, ...) __attribute__((format (printf, 2, 3))) + { + va_list args; + va_start(args, fmt); + msg_.reset(sqlite3_vmprintf(fmt, args)); + va_end(args); + return msg_.get(); + } + +#endif + cstring_ref format(cstring_ref fmt, ...) + { + va_list args; + va_start(args, fmt); + msg_.reset(sqlite3_vmprintf(fmt.c_str(), args)); + va_end(args); + return msg_.get(); + } + + /// use sqlite_snprintf to generate a message +#if defined(__GNUC__) + cstring_ref snformat(const char * fmt, ...) __attribute__((format (printf, 2, 3))) + { + if (capacity() == 0) + return ""; + va_list args; + va_start(args, fmt); + sqlite3_vsnprintf(static_cast<int>(capacity()), msg_.get(), fmt, args); + va_end(args); + return msg_.get(); + } + +#endif + cstring_ref snformat(cstring_ref fmt, ...) + { + if (capacity() == 0) + return ""; + va_list args; + va_start(args, fmt); + sqlite3_vsnprintf(static_cast<int>(capacity()), msg_.get(), fmt.c_str(), args); + va_end(args); + return msg_.get(); + } + /// reserve data in the buffer i.e. allocate + void reserve(std::size_t sz) + { + if (msg_) + { + if (sqlite3_msize(msg_.get()) < sz) + msg_.reset(static_cast<char*>(sqlite3_realloc64(msg_.release(), sz))); + } + else + msg_.reset(static_cast<char*>(sqlite3_malloc64(sz))); + } + + /// Get the allocated memory + std::size_t capacity() const {return msg_ ? sqlite3_msize(msg_.get()) : 0u;} + + /// Gets the error message. + cstring_ref message() const noexcept { return msg_ ? msg_.get() : ""; } + + char * release() + { + return msg_.release(); + } + /// Restores the object to its initial state. + void clear() noexcept { if (msg_) *msg_ = '\0'; } + + + operator bool() const {return msg_.operator bool();} + private: + unique_ptr<char> msg_; +}; + + +/** + * \brief An error containing both a code & optional message. + * \ingroup reference + * \details Contains an error . + */ +struct error +{ + /// The code of the error. + int code; + /// The additional information of the error + error_info info; + + error(int code, error_info info) : code(code), info(std::move(info)) {} + explicit error(int code) : code(code) {} + error(int code, core::string_view info) + : code(code), + info(info) {} + + error(system::error_code code, error_info info) + : code(code.category() == sqlite_category() ? code.value() : SQLITE_FAIL), + info(std::move(info)) {} + + error(system::error_code code) : code(code.category() == sqlite_category() ? code.value() : SQLITE_FAIL) + { + if (code.category() == sqlite_category()) + info = error_info{code.what()}; + } + error() = default; + error(error && ) noexcept = default; +}; + +BOOST_NORETURN BOOST_SQLITE_DECL void throw_exception_from_error( error const & e, boost::source_location const & loc ); + +template<typename T = void> +using result = system::result<T, error>; + +template<typename T> +struct is_result_type : std::false_type {}; + +template<typename T> +struct is_result_type<result<T>> : std::true_type {}; + + +BOOST_SQLITE_END_NAMESPACE + + +#endif // BOOST_SQLITE_ERROR_HPP diff --git a/include/boost/sqlite/extension.hpp b/include/boost/sqlite/extension.hpp new file mode 100644 index 0000000..32553fa --- /dev/null +++ b/include/boost/sqlite/extension.hpp @@ -0,0 +1,67 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_EXTENSION_HPP +#define BOOST_SQLITE_EXTENSION_HPP + +#define BOOST_SQLITE_COMPILE_EXTENSION 1 +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/connection.hpp> +#include <boost/sqlite/detail/catch.hpp> +#include <boost/config.hpp> + + +/** @brief Declare a sqlite module. + @ingroup reference + + @param Name The name of the module + @param Conn The parameter name of the connection + + This macro can be used to create an sqlite extension. + + @note When defining BOOST_SQLITE_COMPILE_EXTENSION (was is done in extension.hpp) + sqlite will use an inline namespace to avoid symbol clashes. + + @par Examples + + @code{.cpp} + + BOOST_SQLITE_EXTENSION(extension, conn) + { + create_scalar_function( + conn, "assert", + [](boost::sqlite::context<>, boost::span<boost::sqlite::value, 1u> sp) + { + if (sp.front().get_int() == 0) + throw std::logic_error("assertion failed"); + }); + } + + @endcode{.cpp} + + */ +#define BOOST_SQLITE_EXTENSION(Name, Conn) \ +void sqlite_##Name##_impl (boost::sqlite::connection Conn); \ +extern "C" BOOST_SYMBOL_EXPORT \ +int sqlite3_##Name##_init( \ + sqlite3 *db, \ + char **pzErrMsg, \ + const sqlite3_api_routines *pApi) \ +{ \ + using boost::sqlite::sqlite3_api; \ + SQLITE_EXTENSION_INIT2(pApi); \ + \ + BOOST_SQLITE_TRY \ + { \ + sqlite_##Name##_impl(boost::sqlite::connection{db, false}); \ + return SQLITE_OK; \ + } \ + BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(*pzErrMsg); \ +} \ +void sqlite_##Name##_impl(boost::sqlite::connection Conn) + +#endif //BOOST_SQLITE_EXTENSION_HPP diff --git a/include/boost/sqlite/field.hpp b/include/boost/sqlite/field.hpp new file mode 100644 index 0000000..20fe4bd --- /dev/null +++ b/include/boost/sqlite/field.hpp @@ -0,0 +1,85 @@ +// 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_FIELD_HPP +#define BOOST_SQLITE_FIELD_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/blob.hpp> +#include <boost/sqlite/cstring_ref.hpp> +#include <boost/sqlite/value.hpp> + +#include <boost/core/detail/string_view.hpp> + + +BOOST_SQLITE_BEGIN_NAMESPACE + +/** @brief A holder for a sqlite field, i.e. something returned from a query. + @ingroup reference + */ +struct field +{ + typedef sqlite_int64 int64; + + /// The type of the value + value_type type() const + { + return static_cast<value_type>( sqlite3_column_type(stm_, col_)); + } + /// Is the held value null + bool is_null() const + { + return type() == value_type::null; + } + /// Is the held value is not null + explicit operator bool () const + { + return type() != value_type::null; + } + /// Returns the value as an `int64`. + int64 get_int() const + { + return sqlite3_column_int64(stm_, col_); + } + /// Returns the value as an `double`. + double get_double() const + { + return sqlite3_column_double(stm_, col_); + } + /// Returns the value as text, i.e. a string_view. Note that this value may be invalidated`. + BOOST_SQLITE_DECL + cstring_ref get_text() const; + /// Returns the value as blob, i.e. raw memory. Note that this value may be invalidated`. + BOOST_SQLITE_DECL + blob_view get_blob() const; + /// Returns the field as a value. + value get_value() const + { + return value(sqlite3_column_value(stm_, col_)); + } + /// Returns the name of the column. + cstring_ref column_name() const + { + return sqlite3_column_name(stm_, col_); + } + /// Returns the name of the table. + cstring_ref table_name() const + { + return sqlite3_column_table_name(stm_, col_); + } + /// Returns the name of the original data source. + cstring_ref column_origin_name() const + { + return sqlite3_column_origin_name(stm_, col_); + } + + private: + friend struct row; + sqlite3_stmt * stm_; + int col_ = -1; +}; + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_FIELD_HPP diff --git a/include/boost/sqlite/function.hpp b/include/boost/sqlite/function.hpp new file mode 100644 index 0000000..14060b6 --- /dev/null +++ b/include/boost/sqlite/function.hpp @@ -0,0 +1,452 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_FUNCTION_HPP +#define BOOST_SQLITE_FUNCTION_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/detail/aggregate_function.hpp> +#include <boost/sqlite/detail/scalar_function.hpp> +#include <boost/sqlite/detail/window_function.hpp> +#include <boost/sqlite/connection.hpp> +#include <boost/sqlite/result.hpp> +#include <boost/sqlite/value.hpp> +#include <boost/sqlite/detail/exception.hpp> +#include <boost/sqlite/cstring_ref.hpp> + +#include <boost/core/span.hpp> +#include <boost/callable_traits/args.hpp> +#include <boost/callable_traits/has_void_return.hpp> +#include <boost/callable_traits/return_type.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + +enum function_flags +{ + deterministic = SQLITE_DETERMINISTIC, + directonly = SQLITE_DIRECTONLY, + subtype = SQLITE_SUBTYPE, + innocuous = SQLITE_INNOCUOUS, + result_subtype = SQLITE_RESULT_SUBTYPE, +#if defined(SQLITE_SELFORDER1) + selforder1 = SQLITE_SELFORDER1, +#else + selforder1 = 0 +#endif +}; + + +/** @brief A context that can be passed into scalar functions. + @ingroup reference + + @tparam Args The argument that can be stored in the context. + + @code{.cpp} + extern sqlite::connection conn; + + sqlite::create_scalar_function( + conn, "my_sum", + [](sqlite::context<std::size_t> ctx, + boost::span<sqlite::value, 1u> args) -> std::size_t + { + auto value = args[0].get_int(); + auto p = ctx.get_if<0>(); + if (p != nullptr) // increment the counter + return (*p) += value; + else // set the initial value + ctx.set<0>(value); + return value; + }); + @endcode + +*/ +template<typename ... Args> +struct context +{ + template<std::size_t N> + using element = mp11::mp_take_c<mp11::mp_list<Args...>, N>; + + /// Set the value in the context at position `Idx` + template<std::size_t Idx> + void set(element<Idx> value) + { + sqlite3_set_auxdata(ctx_, Idx, *static_cast<void**>(&value), + new (memory_tag{}) element<Idx>(std::move(value)), + +[](void * ptr) + { + delete_(static_cast<element<Idx> *>(ptr)); + }); + } + + /// Returns the value in the context at position `Idx`. Throws if the value isn't set. + template<std::size_t Idx> + auto get() -> element<Idx> & + { + using type = element<Idx> ; + auto p = static_cast<type*>(sqlite3_get_auxdata(ctx_, Idx)); + if (p == nullptr) + detail::throw_invalid_argument("argument not set", + BOOST_CURRENT_LOCATION); + return *p; + } + + /// Returns the value in the context at position `Idx`. Returns nullptr .value isn't set. + template<std::size_t Idx> + auto get_if() -> element<Idx> * + { + using type = element<Idx> ; + return static_cast<type*>(sqlite3_get_auxdata(ctx_, Idx)); + } + + + explicit context(sqlite3_context * ctx) noexcept : ctx_(ctx) {} + + /// Set the result through the context, instead of returning it. + template<typename T> + auto set_result(T && val) +#if !defined(BOOST_SQLITE_GENERATING_DOCS) + -> decltype(detail::set_result(static_cast<sqlite3_context*>(nullptr), std::forward<T>(val))) +#endif + { + detail::set_result(ctx_, std::forward<T>(val)); + } + /// Set the an error through the context, instead of throwing it. + void set_error(cstring_ref message, int code = SQLITE_ERROR) + { + sqlite3_result_error(ctx_, message.c_str(), -1); + sqlite3_result_error_code(ctx_, code); + } + /// Returns the connection of the context. + connection get_connection() const + { + return connection{sqlite3_context_db_handle(ctx_), false}; + } + + private: + sqlite3_context * ctx_; +}; + + +///@{ +/** @brief create a scalar function + @ingroup reference + + @param conn The connection to add the function to. + @param name The name of the function + @param func The function to be added + @param ec The system::error_code + + @throws `system::system_error` when the overload without `ec` is used. + + `func` must take `context<Args...>` as the first and a `span<value, N>` as the second value. + If `N` is not `dynamic_extent` it will be used to deduce the number of arguments for the function. + + @par Example + + @code{.cpp} + extern sqlite::connection conn; + + sqlite::create_function( + conn, "my_sum", + [](sqlite::context<std::size_t> ctx, + boost::span<sqlite::value, 1u> args) -> std::size_t + { + auto value = args[0].get_int(); + auto p = ctx.get_if<0>(); + if (p != nullptr) // increment the counter + return (*p) += value; + else // set the initial value + ctx.set<0>(value); + return value; + }); + @endcode + + */ +template<typename Func> +auto create_scalar_function( + connection & conn, + cstring_ref name, + Func && func, + function_flags flags, + system::error_code & ec, + error_info & ei) +#if !defined(BOOST_SQLITE_GENERATING_DOCS) + -> typename std::enable_if< + std::is_same< + decltype( + detail::create_scalar_function( + static_cast<sqlite3*>(nullptr), name, + std::declval<Func>(), flags) + ), int>::value>::type +#endif +{ + auto res = detail::create_scalar_function(conn.handle(), name, + std::forward<Func>(func), static_cast<int>(flags)); + if (res != 0) + { + BOOST_SQLITE_ASSIGN_EC(ec, res); + ei.set_message(sqlite3_errmsg(conn.handle())); + } +} + +///@{ +/** @brief create a scalar function + @ingroup reference + + @param conn The connection to add the function to. + @param name The name of the function + @param func The function to be added + @param ec The system::error_code + + @throws `system::system_error` when the overload without `ec` is used. + + `func` must take `context<Args...>` as the first and a `span<value, N>` as the second value. + If `N` is not `dynamic_extent` it will be used to deduce the number of arguments for the function. + + @par Example + + @code{.cpp} + extern sqlite::connection conn; + + sqlite::create_function( + conn, "to_upper", + [](sqlite::context<> ctx, + boost::span<sqlite::value, 1u> args) -> std::string + { + std::string res; + auto txt = val[0].get_text(); + res.resize(txt.size()); + std::transform(txt.begin(), txt.end(), res.begin(), + [](char c){return std::toupper(c);}); + return value; + }); + @endcode + + */ +template<typename Func> +auto create_scalar_function( + connection & conn, + cstring_ref name, + Func && func, + function_flags flags = {}) + -> typename std::enable_if< + std::is_same< + decltype( + detail::create_scalar_function( + static_cast<sqlite3*>(nullptr), name, + std::declval<Func>(), flags) + ), int>::value>::type +{ + system::error_code ec; + error_info ei; + create_scalar_function(conn, name, std::forward<Func>(func), flags, ec, ei); + if (ec) + detail::throw_error_code(ec, ei); +} +///@} + + +///@{ +/** @brief create a aggregate function + @ingroup reference + + @param conn The connection to add the function to. + @param name The name of the function + @param args + @param ec The system::error_code + +@tparam Func The function to be added + + @throws `system::system_error` when the overload without `ec` is used. + + + `func` needs to be an object with two functions: + + @code{.cpp} + void step(boost::span<sqlite::value, N> args); + T final(); + @endcode + + + + An aggregrate function will create a new `Func` for a new `aggregate` from the args tuple and call `step` for every step. + When the aggregation is done `final` is called and the result is returned to sqlite. + + @par Example + + @code{.cpp} + extern sqlite::connection conn; + + struct aggregate_func + { + aggregate_func(std::size_t init) : counter(init) {} + std::size_t counter; + void step(, boost::span<sqlite::value, 1u> val) + { + counter += val[0].get_text().size(); + } + + std::size_t final() + { + return counter; + } + }; + + sqlite::create_function<aggregate_func>( + conn, "char_counter", std::make_tuple(42)); + + @endcode + + */ +template<typename Func, typename Args = std::tuple<>> +void create_aggregate_function( + connection & conn, + cstring_ref name, + Args && args, + function_flags flags, + system::error_code & ec, + error_info & ei) +{ + using func_type = typename std::decay<Func>::type; + auto res = detail::create_aggregate_function<Func>( + conn.handle(), name, std::forward<Args>(args), static_cast<int>(flags), + callable_traits::has_void_return<decltype(&func_type::step)>{} + ); + if (res != 0) + { + BOOST_SQLITE_ASSIGN_EC(ec, res); + ei.set_message(sqlite3_errmsg(conn.handle())); + } +} + +template<typename Func, typename Args = std::tuple<>> +void create_aggregate_function( + connection & conn, + cstring_ref name, + Args && args= {}, + function_flags flags = {}) +{ + system::error_code ec; + error_info ei; + create_aggregate_function<Func>(conn, name, std::forward<Args>(args), flags, ec, ei); + if (ec) + detail::throw_error_code(ec, ei); +} +///@} + +#if SQLITE_VERSION_NUMBER >= 3025000 + +///@{ +/** @brief create a aggregate window function + @ingroup reference + + @param conn The connection to add the function to. + @param name The name of the function + @param args The arguments to construct Func from. + @param ec The system::error_code + +@tparam Func The function to be added + + @throws `system::system_error` when the overload without `ec` is used. + + `func` needs to be an object with three functions: + + @code{.cpp} + void step(boost::span<sqlite::value, N> args); + void inverse(boost::span<sqlite::value, N> args); + T final(); + @endcode + + `State` can be any type and will get deduced together with `N`. + An aggregrate function will create a new `State` for a new `aggregate` and call `step` for every step. + When an element is removed from the window `inverse` is called. + When the aggregation is done `final` is called and the result is returned to sqlite. + + @par Example + + @code{.cpp} + extern sqlite::connection conn; + + struct window_func + { + std::size_t counter; + void step(boost::span<sqlite::value, 1u> val) + { + counter += val[0].get_text().size(); + } + void inverse(boost::span<sqlite::value, 1u> val) + { + counter -= val[0].get_text().size(); + } + + std::size_t final() + { + return counter; + } + }; + + sqlite::create_function( + conn, "win_char_counter", + aggregate_func{}); + @endcode + */ +template<typename Func, typename Args = std::tuple<>> +void create_window_function( + connection & conn, + cstring_ref name, + Args && args, + function_flags flags, + system::error_code & ec) +{ + using func_type = typename std::decay<Func>::type; + auto res = detail::create_window_function<Func>( + conn.handle(), name, std::forward<Args>(args), static_cast<int>(flags), + callable_traits::has_void_return<decltype(&func_type::step)>{}); + if (res != 0) + BOOST_SQLITE_ASSIGN_EC(ec, res); +} + +template<typename Func, typename Args = std::tuple<>> +void create_window_function( + connection & conn, + cstring_ref name, + Args && args = {}, + function_flags flags = {}) +{ + system::error_code ec; + create_window_function<Func>(conn, name, std::forward<Args>(args), flags, ec); + if (ec) + detail::throw_error_code(ec); +} + +///@} + +///@{ +/// Delete function + +inline void delete_function(connection & conn, cstring_ref name, int argc, system::error_code &ec) +{ + auto res = sqlite3_create_function_v2(conn.handle(), name.c_str(), argc, 0, nullptr, nullptr, nullptr, nullptr, nullptr); + if (res != 0) + BOOST_SQLITE_ASSIGN_EC(ec, res); + +} + +inline void delete_function(connection & conn, cstring_ref name, int argc = -1) +{ + system::error_code ec; + delete_function(conn, name, argc, ec); + if (ec) + detail::throw_error_code(ec); +} + +///@} +#endif + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_FUNCTION_HPP diff --git a/include/boost/sqlite/hooks.hpp b/include/boost/sqlite/hooks.hpp new file mode 100644 index 0000000..50cb4fc --- /dev/null +++ b/include/boost/sqlite/hooks.hpp @@ -0,0 +1,370 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_HOOKS_HPP +#define BOOST_SQLITE_HOOKS_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/function.hpp> +#include <boost/system/result.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + + +#if defined(SQLITE_ENABLE_PREUPDATE_HOOK) +/** The context for pre-update events + + @note This is only available if sqlite was compiled with `SQLITE_ENABLE_PREUPDATE_HOOK` enabled. + + */ +struct preupdate_context +{ + /// Returns the old value, i.e. the value before the update. + system::result<value> old(int column) const + { + sqlite3_value * val; + int res = sqlite3_preupdate_old(db_, column, &val); + if (res != 0) + BOOST_SQLITE_RETURN_EC(res); + return value(val); + } + /// The count of colums to be updated + int count() const { return sqlite3_preupdate_count(db_); } + /// The nesting depth of the update. + int depth() const { return sqlite3_preupdate_depth(db_); } + /// The new value to be written to column + system::result<value> new_(int column) const + { + sqlite3_value * val; + int res = sqlite3_preupdate_new(db_, column, &val); + if (res != 0) + BOOST_SQLITE_RETURN_EC(res); + return value(val); + } + + /// @brief Query the status of blob access, e.g. when using @ref blob_handle + /// @see https://www.sqlite.org/c3ref/preupdate_blobwrite.html + int blob_write() const { return sqlite3_preupdate_blobwrite(db_); } + + explicit preupdate_context(sqlite3 * db) noexcept : db_(db) {} + private: + sqlite3 * db_; +}; + +#endif + +namespace detail +{ + + +template<typename Func> +bool commit_hook_impl(sqlite3 * db, + Func * func, + std::true_type) +{ + static_assert(noexcept(func()), "hook must be noexcept"); + return sqlite3_commit_hook( db, func, [](void * data) { return (*static_cast<Func *>(data))() ? 1 : 0; }, nullptr) != nullptr; +} + + +template<typename Func> +bool commit_hook_impl(sqlite3 * db, + Func && func, + std::false_type) +{ + static_assert(noexcept(func()), "hook must be noexcept"); + return sqlite3_commit_hook( + db, + [](void * data) { return (*static_cast<Func *>(data))() ? 1 : 0; }, + &func) != nullptr; +} + +inline bool commit_hook(sqlite3 * db, std::nullptr_t, std::false_type) +{ + return sqlite3_commit_hook(db, nullptr, nullptr); +} + + + +template<typename Func> +bool commit_hook(sqlite3 * db, + Func && func) +{ + using func_type = typename std::decay<Func>::type; + return commit_hook_impl(db, std::forward<Func>(func), std::is_pointer<func_type>{}); +} + + + +template<typename Func> +bool rollback_hook_impl(sqlite3 * db, + Func * func, + std::true_type) +{ + static_assert(noexcept(func()), "hook must be noexcept"); + return sqlite3_rollback_hook( db, func, [](void * data) { (*static_cast<Func *>(data))(); }, nullptr) != nullptr; +} + + +template<typename Func> +bool rollback_hook_impl(sqlite3 * db, + Func && func, + std::false_type) +{ + static_assert(noexcept(func()), "hook must be noexcept"); + return sqlite3_rollback_hook( + db, + [](void * data) { (*static_cast<Func *>(data))(); }, + &func) != nullptr; +} + +inline bool rollback_hook_impl(sqlite3 * db, std::nullptr_t, std::false_type) +{ + return sqlite3_rollback_hook(db, nullptr, nullptr); +} + +template<typename Func> +bool rollback_hook(sqlite3 * db, + Func && func) +{ + using func_type = typename std::decay<Func>::type; + return rollback_hook_impl(db, std::forward<Func>(func), std::is_pointer<func_type>{}); +} + +#if defined(SQLITE_ENABLE_PREUPDATE_HOOK) + +template<typename Func> +bool preupdate_hook_impl(sqlite3 * db, + Func * func, + std::true_type) +{ + static_assert(noexcept(func(preupdate_context(nullptr), SQLITE_SELECT, "", "", 0, 0)), "hook must be noexcept"); + return sqlite3_preupdate_hook( + db, func, + [](void * data, + sqlite3* db, + int op, + const char * db_name, + const char * table_name, + sqlite_int64 key1, + sqlite_int64 key2) + { + (*static_cast<Func *>(data))(preupdate_context(db), op, db_name, table_name, key1, key2); + }, nullptr) != nullptr; +} + + +template<typename Func> +bool preupdate_hook_impl(sqlite3 * db, + Func & func, + std::false_type) +{ + static_assert(noexcept(func(preupdate_context(nullptr), SQLITE_SELECT, "", "", 0, 0)), + "hooks but be noexcept"); + using func_type = typename std::decay<Func>::type; + + return sqlite3_preupdate_hook( + db, + [](void * data, + sqlite3* db, + int op, + const char * db_name, + const char * table_name, + sqlite_int64 key1, + sqlite_int64 key2) + { + (*static_cast<Func *>(data))(preupdate_context(db), op, db_name, table_name, key1, key2); + }, &func) != nullptr; +} + +inline bool preupdate_hook_impl(sqlite3 * db, std::nullptr_t, std::false_type) +{ + return sqlite3_preupdate_hook(db, nullptr, nullptr); +} + +template<typename Func> +bool preupdate_hook(sqlite3 * db, + Func && func) +{ + using func_type = typename std::decay<Func>::type; + return preupdate_hook_impl(db, std::forward<Func>(func), std::is_pointer<func_type>{}); +} + +#endif + +template<typename Func> +bool update_hook_impl(sqlite3 * db, + Func * func, + std::true_type) +{ + static_assert(noexcept(func(SQLITE_SELECT, "", "", 0)), "hook must be noexcept"); + return sqlite3_update_hook( + db, func, + [](void * data, + sqlite3*, + int op, + const char * db, + const char * name, + sqlite_int64 key) + { + (*static_cast<Func *>(data))(op, db, name, key); + }, nullptr) != nullptr; +} + + +template<typename Func> +bool update_hook_impl(sqlite3 * db, + Func & func, + std::false_type) +{ + static_assert(noexcept(func(SQLITE_SELECT, "", "", 0)), "hook must be noexcept"); + using func_type = typename std::decay<Func>::type; + + return sqlite3_update_hook( + db, + [](void * data, + int op, + const char * db, + const char * name, + sqlite_int64 key) + { + (*static_cast<func_type*>(data))(op, db, name, key); + }, &func) != nullptr; +} + +inline +bool update_hook_impl(sqlite3 * db, + std::nullptr_t, + std::false_type) +{ + return sqlite3_update_hook(db, nullptr, nullptr); +} + +template<typename Func> +bool update_hook(sqlite3 * db, + Func && func) +{ + using func_type = typename std::decay<Func>::type; + return update_hook_impl(db, std::forward<Func>(func), std::is_pointer<func_type>{}); +} + + +} + +/** + @brief Install a commit hook + @ingroup reference + + @see [related sqlite documentation](https://www.sqlite.org/c3ref/commit_hook.html) + + The commit hook gets called before a commit gets performed. + If `func` returns true, the commit goes, otherwise it gets rolled back. + + @note If the function is not a free function pointer, this function will *NOT* take ownership. + + @note If `func` is a `nullptr` the hook gets reset. + + @param conn The database connection to install the hook in + @param func The hook function + @return true if an hook has been replaced. + */ +template<typename Func> +bool commit_hook(connection & conn, Func && func) +{ + return detail::commit_hook(conn.handle(), std::forward<Func>(func)); +} + +/** + @brief Install a rollback hook + @ingroup reference + + @see [related sqlite documentation](https://www.sqlite.org/c3ref/commit_hook.html) + + The rollback hook gets called when a rollback gets performed. + + @note If the function is not a free function pointer, this function will *NOT* take ownership. + + @note If `func` is a `nullptr` the hook gets reset. + + @param conn The database connection to install the hook in + @param func The hook function + @return true if an hook has been replaced. + */ +template<typename Func> +bool rollback_hook(connection & conn, Func && func) +{ + return detail::rollback_hook(conn.handle(), std::forward<Func>(func)); +} + +#if defined(SQLITE_ENABLE_PREUPDATE_HOOK) +/** A hook for pre-update events. + + @note This is only available if sqlite was compiled with `SQLITE_ENABLE_PREUPDATE_HOOK` enabled. + @ingroup reference + + @see [related sqlite documentation](https://sqlite.org/c3ref/preupdate_count.html) + + The function will get called + + @note If the function is not a free function pointer, this function will *NOT* take ownership. + + @note If `func` is a `nullptr` the hook gets reset. + + @param conn The database connection to install the hook in + @param func The hook function + @return true if an hook has been replaced. + + The signature of the handler is as following (it must noexcept): + + @code{.cpp} + void preupdate_hook(sqlite::preupdate_context ctx, + int op, + const char * db_name, + const char * table_name, + sqlite3_int64 current_key, + sqlite3_int64 new_key); + @endcode + + + +*/ +template<typename Func> +bool preupdate_hook(connection & conn, Func && func) +{ + return detail::preupdate_hook(conn.handle(), std::forward<Func>(func)); +} + +#endif + +/** + @brief Install an update hook + @ingroup reference + + @see [related sqlite documentation](https://www.sqlite.org/c3ref/update_hook.html) + + The update hook gets called when an update was performed. + + @note If the function is not a free function pointer, this function will *NOT* take ownership. + + The signature of the function is `void(int op, core::string_view db, core::string_view table, sqlite3_int64 id)`. + `op` is either `SQLITE_INSERT`, `SQLITE_DELETE` and `SQLITE_UPDATE`. + + @note If `func` is a `nullptr` the hook gets reset. + + @param conn The database connection to install the hook in + @param func The hook function + @return true if an hook has been replaced. + */ +template<typename Func> +bool update_hook(connection & conn, Func && func) +{ + return detail::update_hook(conn.handle(), std::forward<Func>(func)); +} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_HOOKS_HPP diff --git a/include/boost/sqlite/json.hpp b/include/boost/sqlite/json.hpp new file mode 100644 index 0000000..3409e56 --- /dev/null +++ b/include/boost/sqlite/json.hpp @@ -0,0 +1,140 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_JSON_HPP +#define BOOST_SQLITE_JSON_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/field.hpp> +#include <boost/sqlite/resultset.hpp> +#include <boost/sqlite/value.hpp> +#include <boost/json/parse.hpp> +#include <boost/json/serializer.hpp> +#include <boost/json/storage_ptr.hpp> +#include <boost/json/value_from.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + +struct resultset; +struct field; +struct value; + +/// @brief The subtype value used by the sqlite json extension. See the [sqlite reference](https://www.sqlite.org/json1.html) +constexpr int json_subtype = static_cast<int>('J'); + +inline void tag_invoke(const struct set_result_tag &, sqlite3_context * ctx, const json::value & value) +{ + json::serializer ser; + ser.reset(&value); + + sqlite3_int64 len = 4096; + unique_ptr<char> c{static_cast<char*>(sqlite3_malloc64(len))}; + + len = sqlite3_msize(c.get()); + auto v = ser.read(c.get(), len); + + while (!ser.done()) + { + auto l = v.size(); + len *= 2; + c.reset(static_cast<char*>(sqlite3_realloc(c.release(), len))); + v = ser.read(c.get() + l, len); + } + + sqlite3_result_text(ctx, c.release(), v.size(), sqlite3_free); + sqlite3_result_subtype(ctx, json_subtype); +} + +///@{ +/// @brief Check if the value or field is a json. @ingroup reference +inline bool is_json(const value & v) { return v.type() == value_type::text && v.subtype() == json_subtype; } +inline bool is_json(const field & f) { return f.type() == value_type::text && f.get_value().subtype() == json_subtype; } +///@} + +///@{ +/// @brief Convert the value or field to a json. @ingroup reference +inline json::value as_json(const value & v, json::storage_ptr ptr = {}) +{ + return json::parse(v.get_text(), ptr); +} +inline json::value as_json(const field & f, json::storage_ptr ptr = {}) +{ + return json::parse(f.get_text(), ptr); +} +///@} + + +inline void tag_invoke( const json::value_from_tag &, json::value& val, const value & f) +{ + switch (f.type()) + { + case value_type::integer: + val.emplace_int64() = f.get_int(); + break; + case value_type::floating: + val.emplace_double() = f.get_double(); + break; + case value_type::text: + { + auto txt = f.get_text(); + if (f.subtype() == json_subtype) + val = json::parse(txt, val.storage()); + else + val.emplace_string() = txt; + } + break; + case value_type::blob: + throw_exception(std::invalid_argument("cannot convert blob to json")); + case value_type::null: + default: + val.emplace_null(); + } +} + +inline void tag_invoke( const json::value_from_tag &, json::value& val, const field & f) +{ + switch (f.type()) + { + case value_type::integer: + val.emplace_int64() = f.get_int(); + break; + case value_type::floating: + val.emplace_double() = f.get_double(); + break; + case value_type::text: + { + auto txt = f.get_text(); + if (f.get_value().subtype() == json_subtype) + val = json::parse(txt, val.storage()); + else + val.emplace_string() = txt; + } + break; + case value_type::blob: + throw_exception(std::invalid_argument("cannot convert blob to json")); + case value_type::null: + default: + val.emplace_null(); + } +} + +inline void tag_invoke( const json::value_from_tag &, json::value& val, resultset && rs) +{ + auto & obj = val.emplace_array(); + + for (auto r : rs) + { + auto & row = obj.emplace_back(json::object(obj.storage())).get_object(); + for (auto c : r) + row[c.column_name()] = json::value_from(c, row.storage()); + } +} + +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_JSON_HPP diff --git a/include/boost/sqlite/memory.hpp b/include/boost/sqlite/memory.hpp new file mode 100644 index 0000000..e136a0e --- /dev/null +++ b/include/boost/sqlite/memory.hpp @@ -0,0 +1,105 @@ +// Copyright (c) 2023 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_MEMORY_HPP +#define BOOST_SQLITE_MEMORY_HPP + +#include <boost/sqlite/detail/config.hpp> + +#include <memory> + +BOOST_SQLITE_BEGIN_NAMESPACE +/// A tag to allow `operator new` +/// @ingroup reference +struct memory_tag {}; +BOOST_SQLITE_END_NAMESPACE + + +inline void *operator new ( std::size_t size, boost::sqlite::memory_tag) noexcept +{ + using namespace boost::sqlite; + return sqlite3_malloc64(size); +} +inline void *operator new[]( std::size_t size, boost::sqlite::memory_tag) noexcept +{ + using namespace boost::sqlite; + return sqlite3_malloc64(size); +} + +inline void operator delete ( void* ptr, boost::sqlite::memory_tag) noexcept +{ + using namespace boost::sqlite; + return sqlite3_free(ptr); +} + +BOOST_SQLITE_BEGIN_NAMESPACE + +template<typename T> +void delete_(T * t) +{ + struct scoped_free + { + void * p; + ~scoped_free() + { + sqlite3_free(p); + } + }; + scoped_free _{t}; + t->~T(); +} + +namespace detail +{ + +template<typename T> +struct deleter +{ + void operator()(T* t) + { + delete_(t); + } +}; + +template<typename T> +struct deleter<T[]> +{ + static_assert(std::is_trivially_destructible<T>::value, "T[] needs to be trivially destructible"); + void operator()(T* t) + { + sqlite3_free(t); + } +}; + +template<> +struct deleter<void> +{ + void operator()(void* t) + { + sqlite3_free(t); + } +}; +} + +template<typename T> +using unique_ptr = std::unique_ptr<T, detail::deleter<T>>; + +template<typename T> +inline std::size_t msize(const unique_ptr<T> & ptr) +{ + return sqlite3_msize(ptr.get()); +} + +template<typename T, typename ... Args> +unique_ptr<T> make_unique(Args && ... args) +{ + unique_ptr<void> up{sqlite3_malloc64(sizeof(T))}; + unique_ptr<T> res{new (up.get()) T(std::forward<Args>(args)...)}; + up.release(); + return res; +} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_MEMORY_HPP diff --git a/include/boost/sqlite/meta_data.hpp b/include/boost/sqlite/meta_data.hpp new file mode 100644 index 0000000..00f6527 --- /dev/null +++ b/include/boost/sqlite/meta_data.hpp @@ -0,0 +1,58 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_META_DATA_HPP +#define BOOST_SQLITE_META_DATA_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/connection.hpp> +#include <boost/sqlite/cstring_ref.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + + +struct connection ; + +/// The metadata of a column +struct column_meta_data +{ + /// Data type fo the column + cstring_ref data_type; + /// Name of default collation sequence + cstring_ref collation; + /// true if column has a NOT NULL constraint + bool not_null; + /// true if column is part of the PRIMARY KEY + bool primary_key; + /// true if column is AUTOINCREMENT + bool auto_increment; +}; + +///@{ +/// get the meta-data of one colum +BOOST_SQLITE_DECL +column_meta_data table_column_meta_data(connection & conn, + cstring_ref db_name, cstring_ref table_name, cstring_ref column_name, + system::error_code & ec, error_info &ei); +BOOST_SQLITE_DECL +column_meta_data table_column_meta_data(connection & conn, + cstring_ref table_name, cstring_ref column_name, + system::error_code & ec, error_info &ei); + +BOOST_SQLITE_DECL +column_meta_data table_column_meta_data(connection & conn, + cstring_ref db_name, cstring_ref table_name, cstring_ref column_name); +BOOST_SQLITE_DECL +column_meta_data table_column_meta_data(connection & conn, + cstring_ref table_name, cstring_ref column_name); +///@} + +/// +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_META_DATA_HPP diff --git a/include/boost/sqlite/mutex.hpp b/include/boost/sqlite/mutex.hpp new file mode 100644 index 0000000..63caf0e --- /dev/null +++ b/include/boost/sqlite/mutex.hpp @@ -0,0 +1,55 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_MUTEX_HPP +#define BOOST_SQLITE_MUTEX_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <memory> + +BOOST_SQLITE_BEGIN_NAMESPACE +/// A mutex class that maybe a noop depending on the mode sqlite3 was compiled as. +struct mutex +{ + bool try_lock() + { + if (!impl_) + return false; + return sqlite3_mutex_try(impl_.get()) == SQLITE_OK; + } + void lock() { sqlite3_mutex_enter(impl_.get()); } + void unlock() { sqlite3_mutex_leave(impl_.get()); } + + mutex() : impl_(sqlite3_mutex_alloc(SQLITE_MUTEX_FAST)) {} + mutex(mutex && ) = delete; + private: + struct deleter_ {void operator()(sqlite3_mutex *mtx) {sqlite3_mutex_free(mtx);}}; + std::unique_ptr<sqlite3_mutex, deleter_> impl_; +}; + +/// A recursive mutex class that maybe a noop depending on the mode sqlite3 was compiled as. +struct recursive_mutex +{ + bool try_lock() + { + if (!impl_) + return false; + return sqlite3_mutex_try(impl_.get()) == SQLITE_OK; + } + void lock() { sqlite3_mutex_enter(impl_.get()); } + void unlock() { sqlite3_mutex_leave(impl_.get()); } + + recursive_mutex() : impl_(sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE)) {} + recursive_mutex(recursive_mutex && ) = delete; + private: + struct deleter_ {void operator()(sqlite3_mutex *mtx) {sqlite3_mutex_free(mtx);}}; + std::unique_ptr<sqlite3_mutex, deleter_> impl_; +}; + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_MUTEX_HPP diff --git a/include/boost/sqlite/result.hpp b/include/boost/sqlite/result.hpp new file mode 100644 index 0000000..b609eb4 --- /dev/null +++ b/include/boost/sqlite/result.hpp @@ -0,0 +1,147 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_RESULT_HPP +#define BOOST_SQLITE_RESULT_HPP + +#include <boost/sqlite/blob.hpp> +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/value.hpp> + +#include <boost/variant2/variant.hpp> + +#include <type_traits> + + +BOOST_SQLITE_BEGIN_NAMESPACE + + +struct set_result_tag {}; + + +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, blob b) +{ + auto sz = b.size(); + sqlite3_result_blob(ctx, std::move(b).release(), sz, &operator delete); +} + + +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, zero_blob zb) +{ + sqlite3_result_zeroblob64(ctx, static_cast<sqlite3_uint64>(zb)); +} + +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, double dbl) { sqlite3_result_double(ctx, dbl); } + +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, sqlite3_int64 value) +{ + sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(value)); +} + +template<typename = std::enable_if_t<!std::is_same<std::int64_t, sqlite3_int64>::value>> +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::int64_t value) + +{ + sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(value)); +} + +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::nullptr_t) { sqlite3_result_null(ctx); } +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, string_view str) +{ + sqlite3_result_text(ctx, str.data(), str.size(), SQLITE_TRANSIENT); +} +template<typename String> +inline auto tag_invoke(set_result_tag, sqlite3_context * ctx, String && str) + -> typename std::enable_if<std::is_convertible<String, string_view>::value>::type +{ + return tag_invoke(set_result_tag{}, ctx, string_view(str)); +} + + +inline void tag_invoke(set_result_tag, sqlite3_context * , variant2::monostate) { } +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, const value & val) +{ + sqlite3_result_value(ctx, val.handle()); +} + +struct set_variant_result +{ + sqlite3_context * ctx; + template<typename T> + void operator()(T && val) + { + tag_invoke(set_result_tag{}, ctx, std::forward<T>(val)); + } +}; + +template<typename ... Args> +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, const variant2::variant<Args...> & var) +{ + visit(set_variant_result{ctx}, var); +} + +template<typename T> +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr<T> ptr) +{ + sqlite3_result_pointer(ctx, ptr.release(), typeid(T).name(), +[](void * ptr){delete static_cast<T*>(ptr);}); +} + +template<typename T> +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr<T, void(*)(T*)> ptr) +{ + sqlite3_result_pointer(ctx, ptr.release(), typeid(T).name(), static_cast<void(*)(void*)>(ptr.get_deleter())); +} + +template<typename T, typename Deleter> +inline auto tag_invoke(set_result_tag, sqlite3_context * ctx, std::unique_ptr<T, Deleter> ptr) + -> typename std::enable_if<std::is_empty<Deleter>::value && + std::is_default_constructible<Deleter>::value>::type +{ + sqlite3_result_pointer(ctx, ptr.release(), typeid(T).name(), +[](void * ptr){Deleter()(static_cast<T*>(ptr));}); +} + +inline void tag_invoke(set_result_tag, sqlite3_context * ctx, error err) +{ + if (err.info) + sqlite3_result_error(ctx, err.info.message().c_str(), -1); + sqlite3_result_error_code(ctx, err.code); +} + + +template<typename T> +inline void tag_invoke(set_result_tag tag, sqlite3_context * ctx, result<T> res) +{ + if (res.has_value()) + tag_invoke(tag, ctx, std::move(res).value()); + else + tag_invoke(tag, ctx, std::move(res).error()); +} + +inline void tag_invoke(set_result_tag tag, sqlite3_context * ctx, result<void> res) +{ + if (res.has_error()) + tag_invoke(tag, ctx, std::move(res).error()); +} + + +namespace detail +{ + +template<typename Value> +inline auto set_result(sqlite3_context * ctx, Value && value) + -> decltype(tag_invoke(set_result_tag{}, ctx, std::forward<Value>(value))) +{ + tag_invoke(set_result_tag{}, ctx, std::forward<Value>(value)); +} + + +} + + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_RESULT_HPP diff --git a/include/boost/sqlite/resultset.hpp b/include/boost/sqlite/resultset.hpp new file mode 100644 index 0000000..7c514f0 --- /dev/null +++ b/include/boost/sqlite/resultset.hpp @@ -0,0 +1,150 @@ +// 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_RESULTSET_HPP +#define BOOST_SQLITE_RESULTSET_HPP + +#include <memory> +#include <boost/sqlite/row.hpp> +#include <boost/describe/members.hpp> + +#include <boost/system/result.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE +struct connection ; +/** + @brief Representation of a result from a database. + @ingroup reference + + If is a forward-range with output iterators. + + @par Example + + @code{.cpp} + + extern sqlite::connection conn; + + sqlite::resultset rs = conn.query("select * from users;"); + + do + { + handle_row(r.current()); + } + while (rs.read_next()) // read it line by line + + + @endcode + +*/ +struct resultset +{ + /// Returns the current row. + row current() const & + { + row r; + r.stm_ = impl_.get(); + return r; + } + /// Checks if the last row has been reached. + bool done() const {return done_;} + + ///@{ + /// Read the next row. Returns false if there's nothing more to read. + BOOST_SQLITE_DECL bool read_next(system::error_code & ec, error_info & ei); + BOOST_SQLITE_DECL bool read_next(); + ///@} + + /// + std::size_t column_count() const + { + return sqlite3_column_count(impl_.get()); + } + /// Returns the name of the column idx. + cstring_ref column_name(std::size_t idx) const + { + return sqlite3_column_name(impl_.get(), static_cast<int>(idx)); + } + /// Returns the name of the source table for column idx. + cstring_ref table_name(std::size_t idx) const + { + return sqlite3_column_table_name(impl_.get(), static_cast<int>(idx)); + } + /// Returns the origin name of the column for column idx. + cstring_ref column_origin_name(std::size_t idx) const + { + return sqlite3_column_origin_name(impl_.get(), static_cast<int>(idx)); + } + + /// The input iterator can be used to read every row in a for-loop + struct iterator + { + using value_type = value; + using difference_type = int; + using reference = value&; + using iterator_category = std::forward_iterator_tag; + + iterator() {} + explicit iterator(sqlite3_stmt * stmt, bool sentinel) : sentinel_(sentinel ) + { + row_.stm_ = stmt; + } + + bool operator!=(iterator rhs) const + { + return sentinel_ != rhs.sentinel_; + } + + row &operator*() { return row_; } + row *operator->() { return &row_; } + + BOOST_SQLITE_DECL + iterator operator++(); + + iterator operator++(int) + { + auto l = *this; + ++(*this); + return l; + } + + private: + row row_; + bool sentinel_ = true; + }; + + /// Return an input iterator to the currently unread row + iterator begin() { return iterator(impl_.get(), done_);} + /// Sentinel iterator. + iterator end() { return iterator(impl_.get(), true); } + + private: + friend struct connection; + friend struct statement; + + struct deleter_ + { + constexpr deleter_() noexcept {} + bool delete_ = true; + void operator()(sqlite3_stmt * sm) + { + if (sqlite3_data_count(sm) > 0) + while ( sqlite3_step(sm) == SQLITE_ROW); + if (delete_) + sqlite3_finalize(sm); + else + { + sqlite3_clear_bindings(sm); + sqlite3_reset(sm); + } + + } + }; + std::unique_ptr<sqlite3_stmt, deleter_> impl_; + bool done_ = false; +}; + +BOOST_SQLITE_END_NAMESPACE + + +#endif //BOOST_SQLITE_RESULTSET_HPP diff --git a/include/boost/sqlite/row.hpp b/include/boost/sqlite/row.hpp new file mode 100644 index 0000000..6893f96 --- /dev/null +++ b/include/boost/sqlite/row.hpp @@ -0,0 +1,167 @@ +// 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_ROW_HPP +#define BOOST_SQLITE_ROW_HPP + +#include <boost/sqlite/field.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + +/** @brief Representation of a row in a database. + @ingroup reference + + Is a random-access range. + + All values that are obtained by view are valid until the next row is read. + + */ +struct row +{ + /// The size of the row + std::size_t size() const + { + return sqlite3_column_count(stm_); + } + /// Returns the field at `idx`, @throws std::out_of_range + BOOST_SQLITE_DECL + field at(std::size_t idx) const; + /// Returns the field at `idx`. + field operator[](std::size_t idx) const + { + field f; + f.stm_ = stm_; + f.col_ = static_cast<int>(idx); + return f; + } + /// Random access iterator used to iterate over the columns. + struct const_iterator + { + using difference_type = int; + using reference = field&; + using iterator_category = std::random_access_iterator_tag; + + const_iterator & operator++() + { + f_.col_++; + return *this; + } + + const_iterator operator++(int) + { + auto last = *this; + ++(*this); + return last; + } + + const_iterator & operator--() + { + f_.col_--; + return *this; + } + + const_iterator operator--(int) + { + auto last = *this; + --(*this); + return last; + } + + field operator[](int i) const + { + auto f = f_; + f.col_ += i; + return f; + } + + const_iterator operator+(int i) const + { + auto r = *this; + r.f_.col_ += i; + return r; + } + + const_iterator operator-(int i) const + { + auto r = *this; + r.f_.col_ -= i; + return r; + } + + const_iterator & operator+=(int i) + { + f_.col_ += i; + return *this; + } + + const_iterator & operator-=(int i) + { + f_.col_ -= i; + return *this; + } + + const field & operator*() const + { + return f_; + } + + const field * operator->() const + { + return &f_; + } + + bool operator==(const const_iterator& other) const + { + return f_.col_ == other.f_.col_ + && f_.stm_ == other.f_.stm_; + } + + bool operator!=(const const_iterator& other) const + { + return f_.col_ != other.f_.col_ + || f_.stm_ != other.f_.stm_; + } + + bool operator<(const const_iterator& other) const + { + return f_.col_ < other.f_.col_ + && f_.stm_ < other.f_.stm_; + } + + bool operator>(const const_iterator& other) const + { + return f_.col_ > other.f_.col_ + && f_.stm_ > other.f_.stm_; + } + + const_iterator() = default; + private: + friend struct row; + field f_; + }; + /// Returns the begin of the column-range. + const_iterator begin() const + { + const_iterator ci; + ci.f_.col_ = 0; + ci.f_.stm_ = stm_; + return ci; + } + /// Returns the end of the column-range. + const_iterator end() const + { + const_iterator ci; + ci.f_.col_ = sqlite3_column_count(stm_); + ci.f_.stm_ = stm_; + return ci; + } + private: + friend struct resultset; + sqlite3_stmt * stm_; + +}; + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_ROW_HPP diff --git a/include/boost/sqlite/statement.hpp b/include/boost/sqlite/statement.hpp new file mode 100644 index 0000000..b77bbe8 --- /dev/null +++ b/include/boost/sqlite/statement.hpp @@ -0,0 +1,651 @@ +// 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 <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/detail/exception.hpp> +#include <boost/sqlite/blob.hpp> +#include <boost/sqlite/resultset.hpp> + +#include <boost/mp11/algorithm.hpp> +#include <boost/core/ignore_unused.hpp> +#include <boost/variant2/variant.hpp> + + +#include <tuple> + +BOOST_SQLITE_BEGIN_NAMESPACE +struct connection; +template<typename, bool> +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<variant2::monostate>{}} {} + /// Bind null + param_ref(std::nullptr_t) : impl_{variant2::in_place_type_t<variant2::monostate>{}} {} + /// Bind an integer. + template<typename I, + typename = typename std::enable_if<std::is_integral<I>::value>::type> + param_ref(I value) + { + BOOST_IF_CONSTEXPR ((sizeof(I) == sizeof(int) && std::is_unsigned<I>::value) + || (sizeof(I) > sizeof(int))) + impl_.emplace<sqlite3_int64>(static_cast<sqlite3_int64>(value)); + else + impl_.emplace<int>(static_cast<int>(value)); + } + /// Bind a blob. + param_ref(blob_view blob) : impl_(blob) { } + /// Bind a string. + param_ref(string_view text) : impl_(text) { } + + template<typename StringLike> + param_ref(StringLike && text, + typename std::enable_if<std::is_constructible<string_view, StringLike>::value>::type * = nullptr) + : impl_(variant2::in_place_type_t<string_view>{}, text) {} + + template<typename BlobLike> + param_ref(BlobLike && text, + typename std::enable_if< + !std::is_constructible<string_view, BlobLike>::value + && std::is_constructible<blob_view, BlobLike>::value>::type * = nullptr) + : impl_(variant2::in_place_type_t<blob_view>{}, 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<typename T> + param_ref(std::unique_ptr<T> ptr) + : impl_(variant2::in_place_index_t<7>{}, + std::unique_ptr<void, void(*)(void*)>( + static_cast<void*>(ptr.release()), + +[](void * ptr){delete static_cast<T*>(ptr);}), + typeid(T).name()) + { + } + + /// Bind pointer value with a function as deleter to the parameter. @see https://www.sqlite.org/bindptr.html + template<typename T> + param_ref(std::unique_ptr<T, void(*)(T*)> ptr) + : impl_(variant2::in_place_index_t<7>{}, + std::unique_ptr<void, void(*)(void*)>( + static_cast<void*>(ptr.release()), + +[](void * ptr){delete static_cast<T*>(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<typename T, typename Deleter> + param_ref(std::unique_ptr<T, Deleter> ptr, + typename std::enable_if<std::is_empty<Deleter>::value && + std::is_default_constructible<Deleter>::value, int>::type * = nullptr) + : impl_(variant2::in_place_index_t<7>{}, + std::unique_ptr<void, void(*)(void*)>( + static_cast<void*>(ptr.release()), + +[](void * ptr){delete static_cast<T*>(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<typename T> + auto operator()(T&& t) const -> typename std::enable_if<std::is_constructible<param_ref, T&&>::value, param_ref>::type + { + return param_ref(std::forward<T>(t)); + } + }; + + public: + /// Construct param_ref from a variant + template<typename T> + param_ref(T && t, + decltype(variant2::visit(make_visitor(), std::forward<T>(t))) * = nullptr) + : param_ref(variant2::visit(make_visitor(), std::forward<T>(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::size_t>(std::numeric_limits<int>::max())) + return sqlite3_bind_blob64(stmt, col, blob.data(), blob.size(), SQLITE_STATIC); + else + return sqlite3_bind_blob(stmt, col, blob.data(), static_cast<int>(blob.size()), SQLITE_STATIC); + } + + int operator()(string_view text) + { + if (text.size() > std::numeric_limits<int>::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<int>(text.size()), SQLITE_STATIC); + } + int operator()(double value) + { + return sqlite3_bind_double(stmt, col, value); + } + int operator()(zero_blob zb) + { + if (static_cast<std::size_t>(zb) > static_cast<std::size_t>(std::numeric_limits<int>::max())) + return sqlite3_bind_zeroblob64(stmt, col, static_cast<sqlite3_uint64>(zb)); + else + return sqlite3_bind_zeroblob(stmt, col, static_cast<int>(zb)); + } +#if SQLITE_VERSION_NUMBER >= 3020000 + int operator()(std::pair<std::unique_ptr<void, void(*)(void*)>, 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<variant2::monostate, int, sqlite3_int64, + blob_view, string_view, double, zero_blob +#if SQLITE_VERSION_NUMBER >= 3020000 + , std::pair<std::unique_ptr<void, void(*)(void*)>, 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 <typename ArgRange = std::initializer_list<param_ref>> + resultset execute( + ArgRange && params, + system::error_code& ec, + error_info& info) && + { + bind_impl(std::forward<ArgRange>(params), ec, info); + resultset rs; + rs.impl_.reset(impl_.release()); + if (!ec) + rs.read_next(ec, info); + return rs; + } + + template <typename ArgRange = std::initializer_list<param_ref>> + resultset execute(ArgRange && params) && + { + system::error_code ec; + error_info ei; + auto tmp = std::move(*this).execute(std::forward<ArgRange>(params), ec, ei); + if (ec) + detail::throw_error_code(ec, ei); + return tmp; + } + + resultset execute( + std::initializer_list<std::pair<string_view, param_ref>> 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<std::pair<string_view, param_ref>> 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<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>> + static_resultset<T, Strict> execute( + ArgRange && params, + system::error_code & ec, + error_info & ei) && + { + static_resultset<T, Strict> tmp = std::move(*this).execute(std::forward<ArgRange>(params), ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>> + static_resultset<T, Strict> execute(ArgRange && params) && + { + system::error_code ec; + error_info ei; + auto tmp = std::move(*this).execute<T>(std::forward<ArgRange>(params), ec, ei); + if (ec) + throw_exception(system::system_error(ec, ei.message())); + return tmp; + } + + template<typename T, bool Strict = false> + static_resultset<T, Strict> execute( + std::initializer_list<std::pair<string_view, param_ref>> params, + system::error_code & ec, + error_info & ei) && + { + static_resultset<T, Strict> tmp = std::move(*this).execute(std::move(params), ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template<typename T, bool Strict = false> + static_resultset<T, Strict> execute(std::initializer_list<std::pair<string_view, param_ref>> params) && + { + system::error_code ec; + error_info ei; + auto tmp = std::move(*this).execute<T, Strict>(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 <typename ArgRange = std::initializer_list<param_ref>> + resultset execute( + ArgRange && params, + system::error_code& ec, + error_info& info) & + { + bind_impl(std::forward<ArgRange>(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 <typename ArgRange = std::initializer_list<param_ref>> + resultset execute(ArgRange && params) & + { + system::error_code ec; + error_info ei; + auto tmp = execute(std::forward<ArgRange>(params), ec, ei); + if (ec) + detail::throw_error_code(ec, ei); + return tmp; + } + + + resultset execute( + std::initializer_list<std::pair<string_view, param_ref>> 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<std::pair<string_view, param_ref>> 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<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>> + static_resultset<T, Strict> execute( + ArgRange && params, + system::error_code & ec, + error_info & ei) & + { + static_resultset<T, Strict> tmp = execute(std::forward<ArgRange>(params), ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template<typename T, bool Strict = false, typename ArgRange = std::initializer_list<param_ref>> + static_resultset<T, Strict> execute(ArgRange && params) & + { + system::error_code ec; + error_info ei; + auto tmp = execute<T, Strict>(std::forward<ArgRange>(params), ec, ei); + if (ec) + throw_exception(system::system_error(ec, ei.message())); + return tmp; + } + + template<typename T, bool Strict = false> + static_resultset<T, Strict> execute( + std::initializer_list<std::pair<string_view, param_ref>> params, + system::error_code & ec, + error_info & ei) & + { + static_resultset<T, Strict> tmp = execute(std::move(params), ec, ei); + if (ec) + return {}; + tmp.check_columns_(ec, ei); + if (ec) + return {}; + + return tmp; + } + + template<typename T, bool Strict = false> + static_resultset<T, Strict> execute(std::initializer_list<std::pair<string_view, param_ref>> params) & + { + system::error_code ec; + error_info ei; + auto tmp = execute<T, Strict>(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<typename ... Args> + void bind_impl(std::tuple<Args...> && vec, + system::error_code & ec, + error_info & ei) + { + const auto sz = sqlite3_bind_parameter_count(impl_.get()); + if (sizeof...(Args) < static_cast<std::size_t>(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<typename ... Args> + void bind_impl(const std::tuple<Args...> & vec, + system::error_code & ec, + error_info & ei) + { + const auto sz = sqlite3_bind_parameter_count(impl_.get()); + if (static_cast<int>(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<typename ParamVector> + void bind_impl(ParamVector && vec, system::error_code & ec, error_info & ei, + typename std::enable_if<std::is_convertible< + typename std::decay<ParamVector>::type::value_type, param_ref>::value>::type * = nullptr) + { + const auto sz = sqlite3_bind_parameter_count(impl_.get()); + if (vec.size() < static_cast<std::size_t>(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<ParamVector>(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<typename ParamMap> + void bind_impl(ParamMap && vec, system::error_code & ec, error_info & ei, + typename std::enable_if< + std::is_convertible<typename std::decay<ParamMap>::type::key_type, string_view>::value && + std::is_convertible<typename std::decay<ParamMap>::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<ParamMap&&>::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<std::pair<string_view, param_ref>> 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<string_view, param_ref> & 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<sqlite3_stmt, deleter_> impl_; +}; + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_STATEMENT_HPP diff --git a/include/boost/sqlite/static_resultset.hpp b/include/boost/sqlite/static_resultset.hpp new file mode 100644 index 0000000..fd6c359 --- /dev/null +++ b/include/boost/sqlite/static_resultset.hpp @@ -0,0 +1,497 @@ +// +// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_STATIC_RESULTSET_HPP +#define BOOST_SQLITE_STATIC_RESULTSET_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/resultset.hpp> +#include <boost/sqlite/connection.hpp> + +#include <boost/describe/members.hpp> + +#include <array> +#include <cstdint> + +#if __cplusplus >= 202002L +#include <boost/pfr/core.hpp> +#include <boost/pfr/core_name.hpp> +#include <boost/pfr/traits.hpp> +#endif + + +namespace boost { template<typename> class optional;} + +#if __cplusplus >= 201702L +#include <optional> +#endif + +BOOST_SQLITE_BEGIN_NAMESPACE + +namespace detail +{ + +inline void convert_field(sqlite_int64 & target, const field & f) {target = f.get_int();} + +template<typename = std::enable_if_t<!std::is_same<std::int64_t, sqlite_int64>::value>> +inline void convert_field(std::int64_t & target, const field & f) +{ + target = static_cast<std::int64_t>(f.get_int()); +} + +inline void convert_field(double & target, const field & f) {target = f.get_double();} + + +template<typename Traits = std::char_traits<char>, typename Allocator = std::allocator<char>> +inline void convert_field(std::basic_string<char, Traits, Allocator> & target, const field & f) +{ + auto t = f.get_text(); + target.assign(t.begin(), t.end()); +} + +inline void convert_field(string_view & target, const field & f) {target = f.get_text();} +inline void convert_field(blob & target, const field & f) {target = blob(f.get_blob());} +inline void convert_field(blob_view & target, const field & f) {target = f.get_blob();} + +#if __cplusplus >= 201702L +template<typename T> +inline void convert_field(std::optional<T> & target, const field & f) +{ + if (f.is_null()) + target.reset(); + else + convert_field(target.emplace(), f); +} +#endif + +template<typename T> +inline void convert_field(boost::optional<T> & target, const field & f) +{ + if (f.is_null()) + target.reset(); + else + return convert_field(target.emplace_back(), f); +} + +template<typename T> +inline constexpr bool field_type_is_nullable(const T& ) {return false;} +#if __cplusplus >= 201702L +template<typename T> +inline bool field_type_is_nullable(const std::optional<T> &) { return true; } +#endif +template<typename T> +inline bool field_type_is_nullable(const boost::optional<T> &) { return true; } + +inline value_type required_field_type(const sqlite3_int64 &) {return value_type::integer;} + +template<typename = std::enable_if_t<!std::is_same<std::int64_t, sqlite_int64>::value>> +inline value_type required_field_type(const std::int64_t &) {return value_type::integer;} + +template<typename Allocator, typename Traits> +inline value_type required_field_type(const std::basic_string<char, Allocator, Traits> & ) +{ + return value_type::text; +} + +inline value_type required_field_type(const string_view &) {return value_type::text;} +inline value_type required_field_type(const blob &) {return value_type::blob;} +inline value_type required_field_type(const blob_view &) {return value_type::blob;} + + +template<typename ... Args> +void check_columns(const std::tuple<Args...> *, const resultset & r, + system::error_code &ec, error_info & ei) +{ + if (r.column_count() != sizeof...(Args)) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH); + ei.format("Tuple size doesn't match column count [%ld != %ld]", sizeof...(Args), r.column_count()); + } +} + +template<bool Strict, typename ... Args> +void convert_row(std::tuple<Args...> & res, const row & r, system::error_code ec, error_info & ei) +{ + std::size_t idx = 0u; + + mp11::tuple_for_each( + res, + [&](auto & v) + { + const auto i = idx++; + const auto & f = r[i]; + BOOST_IF_CONSTEXPR (Strict) + { + if (!ec) // only check if we don't have an error yet. + { + if (f.is_null() && !field_type_is_nullable(v)) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_NOTNULL); + ei.format("unexpected null in column %d", i); + } + else if (f.type() != required_field_type(v)) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_DATATYPE); + ei.format("unexpected type [%s] in column %d, expected [%s]", + value_type_name(f.type()), i, value_type_name(required_field_type(v))); + } + } + } + else + boost::ignore_unused(ec, ei); + + detail::convert_field(v, f); + }); +} + +#if defined(BOOST_DESCRIBE_CXX14) + +template<typename T, typename = typename std::enable_if<describe::has_describe_members<T>::value>::type> +void check_columns(const T *, const resultset & r, + system::error_code &ec, error_info & ei) +{ + using mems = boost::describe::describe_members<T, describe::mod_public>; + constexpr std::size_t sz = mp11::mp_size<mems>(); + if (r.column_count() != sz) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH); + ei.format("Describe size doesn't match column count [%ld != %ld]", sz, r.column_count()); + } + + // columns can be duplicated! + std::array<bool, sz> found; + std::fill(found.begin(), found.end(), false); + + for (std::size_t i = 0ul; i < r.column_count(); i++) + { + bool cfound = false; + boost::mp11::mp_for_each<mp11::mp_iota_c<sz>>( + [&](auto sz) + { + auto d = mp11::mp_at_c<mems, sz>(); + if (d.name == r.column_name(i)) + { + found[sz] = true; + cfound = true; + } + }); + + if (!cfound) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH); + ei.format("Column '%s' not found in described struct.", r.column_name(i).c_str()); + break; + } + } + + if (ec) + return; + + + auto itr = std::find(found.begin(), found.end(), false); + if (itr != found.end()) + { + mp11::mp_with_index<sz>( + std::distance(found.begin(), itr), + [&](auto sz) + { + auto d = mp11::mp_at_c<mems, sz>(); + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH); + ei.format("Described field '%s' not found in resultset struct.", d.name); + }); + } +} + +template<bool Strict, typename T, + typename = typename std::enable_if<describe::has_describe_members<T>::value>::type> +void convert_row(T & res, const row & r, system::error_code ec, error_info & ei) +{ + for (auto && f: r) + { + boost::mp11::mp_for_each<boost::describe::describe_members<T, describe::mod_public> >( + [&](auto D) + { + if (D.name == f.column_name()) + { + auto & r = res.*D.pointer; + BOOST_IF_CONSTEXPR(Strict) + { + if (!ec) + { + if (f.is_null() && !field_type_is_nullable(r)) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_NOTNULL); + ei.format("unexpected null in column %s", D.name); + } + else if (f.type() != required_field_type(r)) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_DATATYPE); + ei.format("unexpected type [%s] in column %s, expected [%s]", + value_type_name(f.type()), D.name, value_type_name(required_field_type(r))); + } + } + } + + detail::convert_field(r, f); + } + }); + } +} + +#endif + +#if __cplusplus >= 202002L + +template<typename T> + requires (std::is_aggregate_v<T> && !describe::has_describe_members<T>::value) +void check_columns(const T *, const resultset & r, + system::error_code &ec, error_info & ei) +{ + constexpr std::size_t sz = pfr::tuple_size_v<T>; + if (r.column_count() != sz) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH); + ei.format("Describe size doesn't match column count [%ld != %ld]", sz, r.column_count()); + } + + // columns can be duplicated! + std::array<bool, sz> found; + std::fill(found.begin(), found.end(), false); + + for (std::size_t i = 0ul; i < r.column_count(); i++) + { + bool cfound = false; + boost::mp11::mp_for_each<mp11::mp_iota_c<sz>>( + [&](auto sz) + { + if (pfr::get_name<sz, T>() == r.column_name(i)) + { + found[sz] = true; + cfound = true; + } + }); + + if (!cfound) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH); + ei.format("Column %s not found in struct.", r.column_name(i).c_str()); + break; + } + } + + if (ec) + return; + + + auto itr = std::find(found.begin(), found.end(), false); + if (itr != found.end()) + { + mp11::mp_with_index<sz>( + std::distance(found.begin(), itr), + [&](auto sz) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_MISMATCH); + auto nm = pfr::get_name<sz, T>(); + ei.format("PFR field %.*s not found in resultset struct.", static_cast<int>(nm.size()), nm.data()); + }); + } +} + +template<bool Strict, typename T> + requires (std::is_aggregate_v<T> && !describe::has_describe_members<T>::value) +void convert_row(T & res, const row & r, system::error_code ec, error_info & ei) +{ + for (auto && f: r) + { + boost::mp11::mp_for_each<mp11::mp_iota_c<pfr::tuple_size_v<T>>>( + [&](auto D) + { + if (pfr::get_name<D, T>() == f.column_name().c_str()) + { + auto & r = pfr::get<D()>(res); + BOOST_IF_CONSTEXPR(Strict) + { + if (!ec) + { + if (f.is_null() && !field_type_is_nullable(r)) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_NOTNULL); + ei.format("unexpected null in column %s", D.name); + } + else if (f.type() != required_field_type(r)) + { + BOOST_SQLITE_ASSIGN_EC(ec, SQLITE_CONSTRAINT_DATATYPE); + ei.format("unexpected type [%s] in column %s, expected [%s]", + value_type_name(f.type()), D.name, value_type_name(required_field_type(r))); + } + } + } + detail::convert_field(r, f); + } + }); + } +} + +#endif + +} + +/** + @brief A typed resultset using a tuple or a described struct. + @ingroup reference + @tparam T The static type of the query. + @tparam Strict Disables implicit conversions. + + If is a forward-range with output iterators. + + @par Example + + @code{.cpp} + + extern sqlite::connection conn; + struct user { std::string first_name; std::string last_name; }; + BOOST_DESCRIBE_STRUCT(user, (), (first_name, last_name)); + + sqlite::resultset rs = conn.query("select first_name, last_name from users;"); + + do + { + user usr = r.current(); + handle_row(u); + } + while (rs.read_next()) // read it line by line + + @endcode + +*/ +template<typename T, bool Strict > +struct static_resultset +{ + /// Returns the current row. + T current() const & + { + system::error_code ec; + error_info ei; + auto tmp = current(ec, ei); + if (ec) + throw_exception(system::system_error(ec, ei.message())); + return tmp; + } + + /// Returns the current row. + T current(system::error_code & ec, error_info & ei) const & + { + T res; + detail::convert_row<Strict>(res, result_.current(), ec, ei); + return res; + } + + /// Checks if the last row has been reached. + bool done() const {return result_.done();} + + ///@{ + /// Read the next row. Returns false if there's nothing more to read. + BOOST_SQLITE_DECL bool read_next(system::error_code & ec, error_info & ei) { return result_.read_next(ec, ei); } + BOOST_SQLITE_DECL bool read_next() { return result_.read_next(); } + ///@} + + /// + std::size_t column_count() const { return result_.column_count(); } + /// Returns the name of the column idx. + core::string_view column_name(std::size_t idx) const { return result_.column_name(idx); } + + /// Returns the name of the source table for column idx. + core::string_view table_name(std::size_t idx) const { return result_.table_name(idx);} + /// Returns the origin name of the column for column idx. + core::string_view column_origin_name(std::size_t idx) const { return result_.column_origin_name(idx);} + + static_resultset() = default; + static_resultset(resultset && result) : result_(std::move(result)) { } + + + static_resultset(static_resultset<T, false> && rhs) : result_(std::move(rhs.result_)) {} + + /// The input iterator can be used to read every row in a for-loop + struct iterator + { + using value_type = T; + using difference_type = int; + using reference = T&; + using iterator_category = std::forward_iterator_tag; + + iterator() + { + + } + explicit iterator(resultset::iterator itr) : itr_(itr) + { + system::error_code ec; + error_info ei; + if (itr->size() > 0ul) + detail::convert_row<Strict>(value_, *itr, ec, ei); + if (ec) + throw_exception(system::system_error(ec, ei.message())); + } + + bool operator!=(iterator rhs) const + { + return itr_ != rhs.itr_; + } + + value_type &operator*() { return value_; } + value_type *operator->() { return &value_; } + + iterator& operator++() + { + ++itr_; + + system::error_code ec; + error_info ei; + if (itr_->size() > 0ul) + detail::convert_row<Strict>(value_, *itr_, ec, ei); + if (ec) + throw_exception(system::system_error(ec, ei.message())); + + return *this; + } + iterator operator++(int) + { + auto l = *this; + ++(*this); + return l; + } + private: + resultset::iterator itr_; + value_type value_; + }; + + /// Return an input iterator to the currently unread row + iterator begin() { return iterator(result_.begin());} + /// Sentinel iterator. + iterator end() { return iterator(result_.end()); } + + + + static_resultset<T, true> strict() && + { + return {std::move(result_)}; + } + private: + + friend struct static_resultset<T, true>; + friend struct connection; + friend struct statement; + resultset result_; + void check_columns_( system::error_code & ec, error_info & ei) + { + detail::check_columns(static_cast<T*>(nullptr), result_, ec, ei); + } +}; + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_STATIC_RESULTSET_HPP diff --git a/include/boost/sqlite/string.hpp b/include/boost/sqlite/string.hpp new file mode 100644 index 0000000..5a81978 --- /dev/null +++ b/include/boost/sqlite/string.hpp @@ -0,0 +1,49 @@ +// Copyright (c) 2023 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_STRING_HPP +#define BOOST_SQLITE_STRING_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/cstring_ref.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + +inline +bool like( + cstring_ref lhs, + cstring_ref rhs, + char escape = '\0') +{ + return sqlite3_strlike(lhs.c_str(), rhs.c_str(), escape) != 0; +} + +inline +bool glob( + cstring_ref lhs, + cstring_ref rhs) +{ + return sqlite3_strglob(lhs.c_str(), rhs.c_str()) != 0; +} + +inline +int icmp( + cstring_ref lhs, + cstring_ref rhs) +{ + return sqlite3_stricmp(lhs.c_str(), rhs.c_str()); +} + +inline +int icmp( + core::string_view lhs, + core::string_view rhs, + std::size_t n) +{ + return sqlite3_strnicmp(lhs.data(), rhs.data(), static_cast<int>(n)); +} + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_STRING_HPP diff --git a/include/boost/sqlite/transaction.hpp b/include/boost/sqlite/transaction.hpp new file mode 100644 index 0000000..b90bdbf --- /dev/null +++ b/include/boost/sqlite/transaction.hpp @@ -0,0 +1,198 @@ +// +// Copyright (c) 2024 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_TRANSACTION_HPP +#define BOOST_SQLITE_TRANSACTION_HPP + +#include <boost/sqlite/connection.hpp> + +BOOST_SQLITE_BEGIN_NAMESPACE + +/** + * @brief A simple transaction guard implementing RAAI for transactions + * @ingroup reference + * + * @par Example + * @code{.cpp} + * sqlite::connection conn; + * conn.connect("./my-database.db"); + * + * sqlite::transaction t{conn}; + * conn.prepare("insert into log (text) values ($1)").execute(std::make_tuple("booting up")); + * t.commit(); + * @endcode + */ +struct transaction +{ + /// The mode of the transaction + enum behaviour {deferred, immediate, exclusive}; + /// A tag to use, to adopt an already initiated transaction. + constexpr static struct adopt_transaction_t {} adopt_transaction{}; + + + /// Create transaction guard on an existing transaction + transaction(connection & conn, adopt_transaction_t) : conn_(conn), completed_(false) + { + } + + + /// Create transaction guard and initiate a transaction + transaction(connection & conn) : conn_(conn) + { + conn.execute("BEGIN"); + completed_ = false; + } + + /// Create transaction guard and initiate a transaction with the defined behaviour + transaction(connection & conn, behaviour b) : conn_(conn) + { + switch (b) + { + case deferred: conn.execute("BEGIN DEFERRED"); break; + case immediate: conn.execute("BEGIN IMMEDIATE"); break; + case exclusive: conn.execute("BEGIN EXCLUSIVE"); break; + } + completed_ = false; + } + + // see https://www.sqlite.org/lang_transaction.html re noexcept + /// rollback the transaction if not committed. + ~transaction() noexcept(SQLITE_VERSION_NUMBER >= 3007011) + { + if (!completed_) + conn_.execute("ROLLBACK"); + } + + ///@{ + /// Commit the transaction. + void commit() + { + conn_.execute("COMMIT"); + completed_ = true; + } + + void commit(system::error_code & ec, error_info & ei) + { + conn_.execute("COMMIT", ec, ei); + completed_ = true; + } + ///@} + + ///@{ + /// Rollback the transaction explicitly. + void rollback() + { + conn_.execute("ROLLBACK"); + completed_ = true; + } + + void rollback(system::error_code & ec, error_info & ei) + { + conn_.execute("ROLLBACK", ec, ei); + completed_ = true; + } + ///@} + + private: + connection & conn_; + bool completed_ = true; +}; + +/** + * @brief A simple transaction guard implementing RAAI for savepoints. Savepoints can be used recursively. + * @ingroup reference + * + * @par Example + * @code{.cpp} + * sqlite::connection conn; + * conn.connect("./my-database.db"); + * + * sqlite::savepoint t{conn, "my-savepoint}; + * conn.prepare("insert into log (text) values ($1)").execute(std::make_tuple("booting up")); + * t.commit(); + * @endcode +*/ +struct savepoint +{ + /// A tag to use, to adopt an already initiated transaction. + constexpr static transaction::adopt_transaction_t adopt_transaction{}; + + /// Create savepoint guard on an existing savepoint + savepoint(connection & conn, std::string name, transaction::adopt_transaction_t) + : conn_(conn), name_(std::move(name)) + { + } + + /// Create transaction guard and initiate it + savepoint(connection & conn, std::string name) : conn_(conn), name_(std::move(name)) + { + conn.execute("SAVEPOINT " + name_); + completed_ = false; + } + + + /// rollback to the savepoint if not committed. + ~savepoint() noexcept(SQLITE_VERSION_NUMBER >= 3007011) + { + if (!completed_) + conn_.execute("ROLLBACK TO " + name_); + } + + ///@{ + /// Commit/Release the transaction. + void commit() + { + conn_.execute("RELEASE " + name_); + completed_ = true; + } + + void commit(system::error_code & ec, error_info & ei) + { + conn_.execute("RELEASE " + name_, ec, ei); + completed_ = true; + } + + void release() + { + conn_.execute("RELEASE " + name_); + completed_ = true; + } + + void release(system::error_code & ec, error_info & ei) + { + conn_.execute("RELEASE " + name_, ec, ei); + completed_ = true; + } + ///@} + + ///@{ + /// Rollback the transaction explicitly. + void rollback() + { + conn_.execute("ROLLBACK TO" + name_); + completed_ = true; + } + + void rollback(system::error_code & ec, error_info & ei) + { + conn_.execute("ROLLBACK TO " + name_, ec, ei); + completed_ = true; + } + ///@} + + /// The name of the savepoint. + const std::string & name() const {return name_;} + private: + connection & conn_; + std::string name_; + bool completed_ = true; +}; + + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_TRANSACTION_HPP diff --git a/include/boost/sqlite/value.hpp b/include/boost/sqlite/value.hpp new file mode 100644 index 0000000..16dc1c1 --- /dev/null +++ b/include/boost/sqlite/value.hpp @@ -0,0 +1,130 @@ +// 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_VALUE_HPP +#define BOOST_SQLITE_VALUE_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/blob.hpp> +#include <boost/sqlite/cstring_ref.hpp> + + +BOOST_SQLITE_BEGIN_NAMESPACE + +/** @brief The type of a value + @ingroup reference + + [related sqlite documentation](https://www.sqlite.org/datatype3.html) +*/ +enum class value_type +{ + /// An integral value + integer = SQLITE_INTEGER, + /// A floating piont value + floating = SQLITE_FLOAT, + /// A textual value + text = SQLITE_TEXT, + /// A binary value + blob = SQLITE_BLOB, + /// No value + null = SQLITE_NULL, +}; + +inline +const char * value_type_name(value_type vt) +{ + switch (vt) + { + case value_type::text: return "text"; + case value_type::blob: return "blob"; + case value_type::floating: return "floating"; + case value_type::integer: return "integer"; + case value_type::null: return "null"; + default: return "unknown"; + } +} + + +/** @brief A holder for a sqlite values used for internal APIs + @ingroup reference + + */ +struct value +{ + // The value for integers in the database + typedef sqlite3_int64 int64 ; + + /// The type of the value + value_type type() const + { + return static_cast<value_type>(sqlite3_value_type(value_)); + } + /// The subtype of the value, see + int subtype() const + { + return sqlite3_value_subtype(value_); + } + /// Is the held value null + bool is_null() const + { + return type() == value_type::null; + } + /// Is the held value is not null + explicit operator bool () const + { + return type() != value_type::null; + } + /// Returns the value as an `integer`. + int64 get_int() const + { + return sqlite3_value_int64(value_); + } + /// Returns the value as an `double`. + double get_double() const + { + return sqlite3_value_double(value_); + } + /// Returns the value as text, i.e. a string_view. Note that this value may be invalidated`. + BOOST_SQLITE_DECL + cstring_ref get_text() const; + /// Returns the value as blob, i.e. raw memory. Note that this value may be invalidated`. + BOOST_SQLITE_DECL + blob_view get_blob() const; + + /// Best numeric datatype of the value + value_type numeric_type() const{return static_cast<value_type>(sqlite3_value_numeric_type(value_));} + +#if SQLITE_VERSION_NUMBER >= 3032000 + /// True if the column is unchanged in an UPDATE against a virtual table. + bool nochange() const {return 0 != sqlite3_value_nochange(value_);} +#endif + +#if SQLITE_VERSION_NUMBER >= 3031000 + /// True if value originated from a bound parameter + bool from_bind() const {return 0 != sqlite3_value_frombind(value_);} +#endif + /// Construct value from a handle. + explicit value(sqlite3_value * value_) noexcept : value_(value_) {} + + /// The handle of the value. + using handle_type = sqlite3_value *; + /// Returns the handle. + handle_type handle() const {return value_;} + handle_type & handle() {return value_;} + +#if SQLITE_VERSION_NUMBER >= 3020000 + /// Get a value that was passed through the pointer interface. + /// A value can be set as a pointer by binding/returning a unique_ptr. + template<typename T> + T * get_pointer() {return static_cast<T*>(sqlite3_value_pointer(value_, typeid(T).name()));} +#endif + private: + sqlite3_value * value_ = nullptr; +}; + +static_assert(sizeof(value) == sizeof(sqlite3_value*), "value must be same as sqlite3_value* pointer"); + +BOOST_SQLITE_END_NAMESPACE + +#endif //BOOST_SQLITE_VALUE_HPP diff --git a/include/boost/sqlite/vtable.hpp b/include/boost/sqlite/vtable.hpp new file mode 100644 index 0000000..a984f4d --- /dev/null +++ b/include/boost/sqlite/vtable.hpp @@ -0,0 +1,615 @@ +// +// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net) +// +// 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_VTABLE_HPP +#define BOOST_SQLITE_VTABLE_HPP + +#include <boost/sqlite/detail/config.hpp> +#include <boost/sqlite/detail/catch.hpp> +#include <boost/sqlite/function.hpp> + +#include <boost/core/span.hpp> +#include <boost/core/demangle.hpp> + +#include <bitset> + +BOOST_SQLITE_BEGIN_NAMESPACE +namespace detail +{ +struct vtab_impl; +} + + +namespace vtab +{ + +/// Helper type to set a function through the xFindFunction callback +struct function_setter +{ + /** Set the function + * + * @tparam Func The function type (either a lambda by ref or a pointer by copy) + * @param func The function to be used + * + * The function can either take a single argument, a `span<sqlite::value, N>` + * for scalar functions, + * or a `context<Args...>` as first, and the span as second for aggegrate functions. + * + */ + template<typename Func> + void set(Func & func) + { + set_impl(func, static_cast<callable_traits::args_t<Func>*>(nullptr)); + } + + template<typename ... Args, std::size_t Extent> + void set(void(* ptr)(context<Args...>, span<value, Extent>)) noexcept + { + *ppArg_ = reinterpret_cast<void*>(ptr); + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) + { + auto cc = context<Args...>(ctx); + auto aa = reinterpret_cast<value*>(args); + auto &f = *reinterpret_cast<void(*)(context<Args...>, span<value, Extent>)>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, cc, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)}); + }; + } + + template<typename T, typename ... Args, std::size_t Extent> + void set(T(* ptr)(context<Args...>, span<value, Extent>)) + { + *ppArg_ = reinterpret_cast<void*>(ptr); + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto cc = context<Args...>(ctx); + auto aa = reinterpret_cast<value*>(args); + auto &f = *reinterpret_cast<T(*)(context<Args...>, span<value, Extent>)>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, cc, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)}); + }; + } + + template<std::size_t Extent> + void set(void(* ptr)(span<value, Extent>)) + { + *ppArg_ = reinterpret_cast<void*>(ptr); + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto aa = reinterpret_cast<value*>(args); + auto &f = *reinterpret_cast<void(*)(span<value, Extent>)>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)}); + }; + } + + template<typename T, std::size_t Extent> + void set(T(* ptr)(span<value, Extent>)) + { + *ppArg_ = reinterpret_cast<void*>(ptr); + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto aa = reinterpret_cast<value*>(args); + auto &f = *reinterpret_cast<T(*)(span<value, Extent>)>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)}); + }; + } + + explicit function_setter(void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg) : pxFunc_(pxFunc), ppArg_(ppArg) {} + private: + + template<typename Func, typename ... Args, std::size_t Extent> + void set_impl(Func & func, std::tuple<context<Args...>, span<value, Extent>>) + { + *ppArg_ = &func; + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto cc = context<Args...>(ctx); + auto aa = reinterpret_cast<value*>(args); + auto &f = *reinterpret_cast<Func*>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, cc, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)}); + }; + } + template<typename Func, std::size_t Extent> + void set_impl(Func & func, std::tuple<span<value, Extent>> * ) + { + *ppArg_ = &func; + *pxFunc_ = + +[](sqlite3_context* ctx, int len, sqlite3_value** args) noexcept + { + auto aa = reinterpret_cast<value*>(args); + auto &f = *reinterpret_cast<Func *>(sqlite3_user_data(ctx)); + detail::execute_context_function(ctx, f, boost::span<value, Extent>{aa, static_cast<std::size_t>(len)}); + }; + } + + void (**pxFunc_)(sqlite3_context*,int,sqlite3_value**); + void **ppArg_; +}; + +#if SQLITE_VERSION_NUMBER >= 3038000 +/** @brief Utility function that can be used in `xFilter` for the `in` operator. + @see https://www.sqlite.org/capi3ref.html#sqlite3_vtab_in_first + @ingroup reference + @Note requires sqlite version >= 3.38 +*/ +struct in +{ + struct iterator + { + iterator() = default; + explicit iterator(sqlite3_value * value ) : value_(value) + { + if (value == nullptr) + return ; + auto res = sqlite3_vtab_in_first(value, &out_.handle()); + if (res != SQLITE_OK) + { + system::error_code ec; + BOOST_SQLITE_ASSIGN_EC(ec, res); + detail::throw_error_code(ec, ec.location()); + } + } + + iterator & operator++() + { + auto res = sqlite3_vtab_in_next(value_, &out_.handle()); + if (res != SQLITE_OK) + { + system::error_code ec; + BOOST_SQLITE_ASSIGN_EC(ec, res); + detail::throw_error_code(ec, ec.location()); + } + return *this; + } + + iterator operator++(int) + { + auto last = *this; + ++(*this); + return last; + } + + + const value & operator*() const + { + return out_; + } + + const value * operator->() const + { + return &out_; + } + + bool operator==(const iterator& other) const + { + return value_ == other.value_ + && out_.handle() == other.out_.handle(); + } + + bool operator!=(const iterator& other) const + { + return value_ != other.value_ + || out_.handle() != other.out_.handle(); + } + + + private: + sqlite3_value * value_{nullptr}; + value out_{nullptr}; + }; + + /// Returns a forward iterator to the `in` sequence for an `in` constraint pointing to the begin. + iterator begin() {return iterator(out_);} + /// Returns a forward iterator to the `in` sequence for an `in` constraint pointing to the end. + iterator end() {return iterator();} + + explicit in(sqlite3_value * out) : out_(out) {} + explicit in(sqlite::value out) : out_(out.handle()) {} + private: + sqlite3_value * out_{nullptr}; +}; + +#endif + +/// index info used by the find_index function +/// @ingroup reference +/// @see [sqlite reference](https://www.sqlite.org/vtab.html#xbestindex) +struct index_info +{ + /// Returns constraints of the index. + BOOST_ATTRIBUTE_NODISCARD + span<const sqlite3_index_info::sqlite3_index_constraint> constraints() const + { + return {info_->aConstraint, static_cast<std::size_t>(info_->nConstraint)}; + } + + /// Returns ordering of the index. + BOOST_ATTRIBUTE_NODISCARD + span<const sqlite3_index_info::sqlite3_index_orderby> order_by() const + { + return {info_->aOrderBy, static_cast<std::size_t>(info_->nOrderBy)}; + } + + BOOST_ATTRIBUTE_NODISCARD + span<sqlite3_index_info::sqlite3_index_constraint_usage> usage() + { + return {info_->aConstraintUsage, static_cast<std::size_t>(info_->nConstraint)}; + } + + BOOST_ATTRIBUTE_NODISCARD + sqlite3_index_info::sqlite3_index_constraint_usage & usage_of( + const sqlite3_index_info::sqlite3_index_constraint & info) + { + auto dist = std::distance(constraints().begin(), &info); + auto itr = usage().begin() + dist; + BOOST_ASSERT(itr < usage().end()); + return *itr; + } + +#if SQLITE_VERSION_NUMBER >= 3022000 + + /// Receive the collation for the contrainst of the position. + const char * collation(std::size_t idx) const + { + return sqlite3_vtab_collation(info_, static_cast<int>(idx)); + } +#endif + + int on_conflict() const {return sqlite3_vtab_on_conflict(db_);} + +#if SQLITE_VERSION_NUMBER >= 3038000 + /// Returns true if the constraint is + bool distinct() const {return sqlite3_vtab_distinct(info_);} + + value * rhs_value(std::size_t idx) const + { + value * v = nullptr; + if (sqlite3_vtab_rhs_value( + info_, static_cast<int>(idx), + reinterpret_cast<sqlite3_value**>(v)) == SQLITE_OK) + return v; + else + return nullptr; + } +#endif + + void set_already_ordered() { info_->orderByConsumed = 1; } + void set_estimated_cost(double cost) { info_->estimatedCost = cost; } +#if SQLITE_VERSION_NUMBER >= 3008200 + void set_estimated_rows(sqlite3_int64 rows) { info_->estimatedRows = rows; } +#endif +#if SQLITE_VERSION_NUMBER >= 3009000 + void set_index_scan_flags(int flags) { info_->idxFlags = flags; } +#endif +#if SQLITE_VERSION_NUMBER >= 3010000 + std::bitset<64u> columns_used() + { + return std::bitset<64u>(info_->colUsed); + } +#endif + + void set_index(int value) { info_->idxNum = value; } + void set_index_string(char * str, + bool take_ownership = true) + { + info_->idxStr = str; + info_->needToFreeIdxStr = take_ownership ? 1 : 0; + } + + sqlite3_index_info * info() const { return info_; } + sqlite3 * db() const { return db_; } + + private: + explicit index_info(sqlite3 * db, sqlite3_index_info * info) : db_(db), info_(info) {} + sqlite3 * db_; + sqlite3_index_info * info_{nullptr}; + friend struct detail::vtab_impl; +}; + + +struct module_config +{ +#if SQLITE_VERSION_NUMBER >= 3031000 + /// @brief Can be used to set SQLITE_VTAB_INNOCUOUS + void set_innocuous() + { + sqlite3_vtab_config(db_, SQLITE_VTAB_INNOCUOUS); + } + /// @brief Can be used to set SQLITE_VTAB_DIRECTONLY + void set_directonly() {sqlite3_vtab_config(db_, SQLITE_VTAB_DIRECTONLY);} + +#endif + + /// @brief Can be used to set SQLITE_VTAB_CONSTRAINT_SUPPORT + void set_constraint_support(bool enabled = false) + { + sqlite3_vtab_config(db_, SQLITE_VTAB_CONSTRAINT_SUPPORT, enabled ? 1 : 0); + } + + private: + explicit module_config(sqlite3 *db) : db_(db) {} + friend struct detail::vtab_impl; + sqlite3 *db_; +}; + +template<typename Table> +struct module +{ + using table_type = Table; + + /// @brief Creates the instance + /// The instance_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab. + /// instance_type must have a member `declaration` that returns a `const char *` for the declaration. + BOOST_SQLITE_VIRTUAL result<table_type> create(sqlite::connection db, int argc, const char * const argv[]) BOOST_SQLITE_PURE; + + /// @brief Create a table + /// The table_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab. + /// table_type must have a member `declaration` that returns a `const char *` for the declaration. + BOOST_SQLITE_VIRTUAL result<table_type> connect(sqlite::connection db, int argc, const char * const argv[]) BOOST_SQLITE_PURE; +}; + +template<typename Table> +struct eponymous_module +{ + using table_type = Table; + + /// @brief Creates the instance + /// The instance_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab. + /// instance_type must have a member `declaration` that returns a `const char *` for the declaration. + BOOST_SQLITE_VIRTUAL result<table_type> connect(sqlite::connection db, int argc, const char * const argv[]) BOOST_SQLITE_PURE; + + eponymous_module(bool eponymous_only = false) : eponymous_only_(eponymous_only) {} + + bool eponymous_only() const {return eponymous_only_;} + protected: + bool eponymous_only_{false}; + +}; + +template<typename ColumnType> +struct cursor; + +/// The basis for vtable +template<typename Cursor> +struct table : protected sqlite3_vtab +{ + using cursor_type = Cursor; + + BOOST_SQLITE_VIRTUAL result<void> config(module_config &) {return {};} + + /// The Table declaration to be used with sqlite3_declare_vtab + BOOST_SQLITE_VIRTUAL const char *declaration() BOOST_SQLITE_PURE; + + /// Destroy the storage = this function needs to be present for non eponymous tables + BOOST_SQLITE_VIRTUAL result<void> destroy() { return {}; } + + /// Tell sqlite how to communicate with the table. + /// Optional, this library will fill in a default function that leaves comparisons to sqlite. + BOOST_SQLITE_VIRTUAL result<void> best_index(index_info & /*info*/) {return {system::in_place_error, SQLITE_OK};} + + /// @brief Start a search on the table. + /// The cursor_type gets used & managed by value, OR a pointer to a class that inherits sqlite3_vtab_cursor. + BOOST_SQLITE_VIRTUAL result<cursor_type> open() BOOST_SQLITE_PURE; + + /// Get the connection of the vtable + sqlite::connection connection() const {return sqlite::connection{db_, false};} + + table(const sqlite::connection & conn) : db_(conn.handle()) {} + table(sqlite3 * db = nullptr) : db_(db) {} + private: + template<typename ColumnType> + friend struct cursor; + friend struct detail::vtab_impl; + + sqlite3 * db_{nullptr}; +}; + +/// Cursor needs the following member. @ingroup reference +template<typename ColumnType = void> +struct cursor : protected sqlite3_vtab_cursor +{ + using column_type = ColumnType; + + /// @brief Apply a filter to the cursor. Required when best_index is implemented. + BOOST_SQLITE_VIRTUAL result<void> filter( + int /*index*/, const char * /*index_data*/, + boost::span<sqlite::value> /*values*/) + { + return {system::in_place_error, SQLITE_OK}; + } + + /// @brief Returns the next row. + BOOST_SQLITE_VIRTUAL result<void> next() BOOST_SQLITE_PURE; + + /// @brief Check if the cursor is and the end + BOOST_SQLITE_VIRTUAL bool eof() BOOST_SQLITE_PURE; + + /// @brief Returns the result of a value. It will use the set_result functionality to create a an sqlite function. + /// see [vtab_no_change](https://www.sqlite.org/c3ref/vtab_nochange.html) + BOOST_SQLITE_VIRTUAL result<column_type> column(int idx, bool no_change) BOOST_SQLITE_PURE; + /// @brief Returns the id of the current row + BOOST_SQLITE_VIRTUAL result<sqlite3_int64> row_id() BOOST_SQLITE_PURE; + + /// Get the table the cursor is pointing to. + vtab::table<cursor> & table() { return *static_cast< vtab::table<cursor>*>(this->pVtab);} + const vtab::table<cursor> & table() const { return *static_cast<const vtab::table<cursor>*>(this->pVtab);} + + friend struct detail::vtab_impl; +}; + +/// Cursor needs the following member. @ingroup reference +template<> +struct cursor<void> : protected sqlite3_vtab_cursor +{ + using column_type = void; + + /// @brief Apply a filter to the cursor. Required when best_index is implemented. + BOOST_SQLITE_VIRTUAL result<void> filter( + int /*index*/, const char * /*index_data*/, + boost::span<sqlite::value> /*values*/) + { + return {system::in_place_error, SQLITE_OK}; + } + + /// @brief Returns the next row. + BOOST_SQLITE_VIRTUAL result<void> next() BOOST_SQLITE_PURE; + + /// @brief Check if the cursor is and the end + BOOST_SQLITE_VIRTUAL bool eof() BOOST_SQLITE_PURE; + + /// @brief Returns the result of a value. It will use the set_result functionality to create a an sqlite function. + /// see [vtab_no_change](https://www.sqlite.org/c3ref/vtab_nochange.html) + BOOST_SQLITE_VIRTUAL void column(context<> ctx, int idx, bool no_change) BOOST_SQLITE_PURE; + /// @brief Returns the id of the current row + BOOST_SQLITE_VIRTUAL result<sqlite3_int64> row_id() BOOST_SQLITE_PURE; + + /// Get the table the cursor is pointing to. + vtab::table<cursor> & table() { return *static_cast< vtab::table<cursor>*>(this->pVtab);} + const vtab::table<cursor> & table() const { return *static_cast<const vtab::table<cursor>*>(this->pVtab);} + + friend struct detail::vtab_impl; +}; + + +/// Group of functions for modifications. @ingroup reference +struct modifiable +{ + BOOST_SQLITE_VIRTUAL result<void> delete_(sqlite::value key) BOOST_SQLITE_PURE; + /// Insert a new row + BOOST_SQLITE_VIRTUAL result<sqlite_int64> insert(sqlite::value key, span<sqlite::value> values, int on_conflict) BOOST_SQLITE_PURE; + /// Update the row + BOOST_SQLITE_VIRTUAL result<sqlite_int64> update(sqlite::value old_key, sqlite::value new_key, span<sqlite::value> values, int on_conflict) BOOST_SQLITE_PURE; +}; + +/// Group of functions to support transactions. @ingroup reference +struct transaction +{ + /// Begin a tranasction + BOOST_SQLITE_VIRTUAL result<void> begin() BOOST_SQLITE_PURE; + /// synchronize the state + BOOST_SQLITE_VIRTUAL result<void> sync() BOOST_SQLITE_PURE; + /// commit the transaction + BOOST_SQLITE_VIRTUAL result<void> commit() BOOST_SQLITE_PURE; + /// rollback the transaction + BOOST_SQLITE_VIRTUAL result<void> rollback() BOOST_SQLITE_PURE; +}; + +/// Fucntion to enable function overriding See `xFindFunction`. +struct overload_functions +{ + /// @see https://www.sqlite.org/vtab.html#xfindfunction + BOOST_SQLITE_VIRTUAL result<void> find_function( + function_setter fs, + int arg, const char * name) BOOST_SQLITE_PURE; +}; + +/// Make the vtable renamable @ingroup reference +struct renamable +{ + /// Function to rename the table. Optional + BOOST_SQLITE_VIRTUAL result<void> rename(const char * new_name) BOOST_SQLITE_PURE; + +}; + +#if SQLITE_VERSION_NUMBER >= 3007007 +/// Support for recursive transactions @ingroup reference +struct recursive_transaction +{ + /// Save the current state with to `i` + BOOST_SQLITE_VIRTUAL result<void> savepoint(int i) BOOST_SQLITE_PURE; + /// Release all saves states down to `i` + BOOST_SQLITE_VIRTUAL result<void> release(int i) BOOST_SQLITE_PURE; + /// Roll the transaction back to `i`. + BOOST_SQLITE_VIRTUAL result<void> rollback_to(int i) BOOST_SQLITE_PURE; +}; +#endif + +} + + +namespace detail +{ + +template<typename Module> +const sqlite3_module make_module(const Module & mod); + +} + +////@{ +/** @brief Register a vtable + @ingroup reference + @see Sqlite vtab reference [vtab](https://www.sqlite.org/vtab.html) + + @param conn The connection to install the vtable into + @param name The name for the vtable + @param module The module to install as a vtable. See @ref vtab_module_prototype for the structure required + @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. + + @param The requirements for `module`. + + @tparam T The implementation type of the module. It must inherit + + @returns A reference to the module as stored in the database. It's lifetime is managed by the database. + + +*/ +template<typename T> +auto create_module(connection & conn, + cstring_ref name, + T && module, + system::error_code & ec, + error_info & ei) -> typename std::decay<T>::type & +{ + using module_type = typename std::decay<T>::type; + static const sqlite3_module mod = detail::make_module(module); + + std::unique_ptr<module_type> p{new (memory_tag{}) module_type(std::forward<T>(module))}; + auto pp = p.get(); + + int res = sqlite3_create_module_v2( + conn.handle(), name.c_str(), + &mod, p.release(), + +[](void * ptr) + { + static_cast<module_type*>(ptr)->~module_type(); + sqlite3_free(ptr); + }); + + if (res != SQLITE_OK) + { + BOOST_SQLITE_ASSIGN_EC(ec, res); + ei.set_message(sqlite3_errmsg(conn.handle())); + } + return *pp; +} + +template<typename T> +auto create_module(connection & conn, + const char * name, + T && module) -> typename std::decay<T>::type & +{ + system::error_code ec; + error_info ei; + T & ref = create_module(conn, name, std::forward<T>(module), ec, ei); + if (ec) + detail::throw_error_code(ec, ei); + return ref; +} +///@} + + +BOOST_SQLITE_END_NAMESPACE + +#include <boost/sqlite/detail/vtable.hpp> + +#endif //BOOST_SQLITE_VTABLE_HPP + |