/// \file nanodbc.cpp Implementation details. #ifndef DOXYGEN // ASCII art banners are helpful for code editors with a minimap display. // Generated with http://patorjk.com/software/taag/#p=display&v=0&f=Colossal #if defined(_MSC_VER) #if _MSC_VER <= 1800 // silence spurious Visual C++ warnings #pragma warning(disable : 4244) // warning about integer conversion issues. #pragma warning(disable : 4312) // warning about 64-bit portability issues. #endif #pragma warning(disable : 4996) // warning about deprecated declaration #endif #include "nanodbc.h" #include #include #include #include #include #include #include #include #ifndef __clang__ #include #endif // User may redefine NANODBC_ASSERT macro in nanodbc.h #ifndef NANODBC_ASSERT #include #define NANODBC_ASSERT(expr) assert(expr) #endif #ifdef NANODBC_USE_BOOST_CONVERT #include #else #include #endif #ifdef __APPLE__ // silence spurious OS X deprecation warnings #define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_6 #endif #ifdef _WIN32 // needs to be included above sql.h for windows #ifndef __MINGW32__ #define NOMINMAX #endif #include #endif #include #include // Driver specific SQL data type defines. // Microsoft has -150 thru -199 reserved for Microsoft SQL Server Native Client driver usage. // Originally, defined in sqlncli.h (old SQL Server Native Client driver) // and msodbcsql.h (new Microsoft ODBC Driver for SQL Server) // See https://github.com/lexicalunit/nanodbc/issues/226 #ifndef SQL_SS_VARIANT #define SQL_SS_VARIANT (-150) #endif #ifndef SQL_SS_XML #define SQL_SS_XML (-152) #endif #ifndef SQL_SS_TABLE #define SQL_SS_TABLE (-153) #endif #ifndef SQL_SS_TIME2 #define SQL_SS_TIME2 (-154) #endif #ifndef SQL_SS_TIMESTAMPOFFSET #define SQL_SS_TIMESTAMPOFFSET (-155) #endif // Large CLR User-Defined Types (ODBC) // https://msdn.microsoft.com/en-us/library/bb677316.aspx // Essentially, UDT is a varbinary type with additional metadata. // Memory layout: SQLCHAR *(unsigned char *) // C data type: SQL_C_BINARY // Value: SQL_BINARY (-2) #ifndef SQL_SS_UDT #define SQL_SS_UDT (-151) // from sqlncli.h #endif #ifndef SQL_NVARCHAR #define SQL_NVARCHAR (-10) #endif // Default to ODBC version defined by NANODBC_ODBC_VERSION if provided. #ifndef NANODBC_ODBC_VERSION #ifdef SQL_OV_ODBC3_80 // Otherwise, use ODBC v3.8 if it's available... #define NANODBC_ODBC_VERSION SQL_OV_ODBC3_80 #else // or fallback to ODBC v3.x. #define NANODBC_ODBC_VERSION SQL_OV_ODBC3 #endif #endif // clang-format off // 888 888 d8b 888 // 888 888 Y8P 888 // 888 888 888 // 888 888 88888b. 888 .d8888b .d88b. .d88888 .d88b. // 888 888 888 "88b 888 d88P" d88""88b d88" 888 d8P Y8b // 888 888 888 888 888 888 888 888 888 888 88888888 // Y88b. .d88P 888 888 888 Y88b. Y88..88P Y88b 888 Y8b. // "Y88888P" 888 888 888 "Y8888P "Y88P" "Y88888 "Y8888 // MARK: Unicode - // clang-format on #ifdef NANODBC_USE_UNICODE #define NANODBC_FUNC(f) f##W #define NANODBC_SQLCHAR SQLWCHAR #else #define NANODBC_FUNC(f) f #define NANODBC_SQLCHAR SQLCHAR #endif #ifdef NANODBC_USE_IODBC_WIDE_STRINGS typedef std::u32string wide_string_type; #define NANODBC_CODECVT_TYPE std::codecvt_utf8 #else #ifdef _MSC_VER typedef std::wstring wide_string_type; #define NANODBC_CODECVT_TYPE std::codecvt_utf8_utf16 #else typedef std::u16string wide_string_type; #define NANODBC_CODECVT_TYPE std::codecvt_utf8_utf16 #endif #endif typedef wide_string_type::value_type wide_char_t; #if defined(_MSC_VER) #ifndef NANODBC_USE_UNICODE // Disable unicode in sqlucode.h on Windows when NANODBC_USE_UNICODE // is not defined. This is required because unicode is enabled by // default on many Windows systems. #define SQL_NOUNICODEMAP #endif #endif // clang-format off // .d88888b. 8888888b. 888888b. .d8888b. 888b d888 // d88P" "Y88b 888 "Y88b 888 "88b d88P Y88b 8888b d8888 // 888 888 888 888 888 .88P 888 888 88888b.d88888 // 888 888 888 888 8888888K. 888 888Y88888P888 8888b. .d8888b 888d888 .d88b. .d8888b // 888 888 888 888 888 "Y88b 888 888 Y888P 888 "88b d88P" 888P" d88""88b 88K // 888 888 888 888 888 888 888 888 888 Y8P 888 .d888888 888 888 888 888 "Y8888b. // Y88b. .d88P 888 .d88P 888 d88P Y88b d88P 888 " 888 888 888 Y88b. 888 Y88..88P X88 // "Y88888P" 8888888P" 8888888P" "Y8888P" 888 888 "Y888888 "Y8888P 888 "Y88P" 88888P' // MARK: ODBC Macros - // clang-format on #define NANODBC_STRINGIZE_I(text) #text #define NANODBC_STRINGIZE(text) NANODBC_STRINGIZE_I(text) // By making all calls to ODBC functions through this macro, we can easily get // runtime debugging information of which ODBC functions are being called, // in what order, and with what parameters by defining NANODBC_ODBC_API_DEBUG. #ifdef NANODBC_ODBC_API_DEBUG #include #define NANODBC_CALL_RC(FUNC, RC, ...) \ do \ { \ std::cerr << __FILE__ \ ":" NANODBC_STRINGIZE(__LINE__) " " NANODBC_STRINGIZE(FUNC) "(" #__VA_ARGS__ ")" \ << std::endl; \ RC = FUNC(__VA_ARGS__); \ } while (false) /**/ #define NANODBC_CALL(FUNC, ...) \ do \ { \ std::cerr << __FILE__ \ ":" NANODBC_STRINGIZE(__LINE__) " " NANODBC_STRINGIZE(FUNC) "(" #__VA_ARGS__ ")" \ << std::endl; \ FUNC(__VA_ARGS__); \ } while (false) /**/ #else #define NANODBC_CALL_RC(FUNC, RC, ...) RC = FUNC(__VA_ARGS__) #define NANODBC_CALL(FUNC, ...) FUNC(__VA_ARGS__) #endif // clang-format off // 8888888888 888 888 888 888 d8b // 888 888 888 888 888 Y8P // 888 888 888 888 888 // 8888888 888d888 888d888 .d88b. 888d888 8888888888 8888b. 88888b. .d88888 888 888 88888b. .d88b. // 888 888P" 888P" d88""88b 888P" 888 888 "88b 888 "88b d88" 888 888 888 888 "88b d88P"88b // 888 888 888 888 888 888 888 888 .d888888 888 888 888 888 888 888 888 888 888 888 // 888 888 888 Y88..88P 888 888 888 888 888 888 888 Y88b 888 888 888 888 888 Y88b 888 // 8888888888 888 888 "Y88P" 888 888 888 "Y888888 888 888 "Y88888 888 888 888 888 "Y88888 // 888 // Y8b d88P // "Y88P" // MARK: Error Handling - // clang-format on namespace { #ifdef NANODBC_ODBC_API_DEBUG inline std::string return_code(RETCODE rc) { switch (rc) { case SQL_SUCCESS: return "SQL_SUCCESS"; case SQL_SUCCESS_WITH_INFO: return "SQL_SUCCESS_WITH_INFO"; case SQL_ERROR: return "SQL_ERROR"; case SQL_INVALID_HANDLE: return "SQL_INVALID_HANDLE"; case SQL_NO_DATA: return "SQL_NO_DATA"; case SQL_NEED_DATA: return "SQL_NEED_DATA"; case SQL_STILL_EXECUTING: return "SQL_STILL_EXECUTING"; } NANODBC_ASSERT(0); return "unknown"; // should never make it here } #endif // Easy way to check if a return code signifies success. inline bool success(RETCODE rc) { #ifdef NANODBC_ODBC_API_DEBUG std::cerr << "<-- rc: " << return_code(rc) << " | " << std::endl; #endif return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO; } // Returns the array size. template inline std::size_t arrlen(T (&)[N]) { return N; } // Operates like strlen() on a character array. template inline std::size_t strarrlen(T (&a)[N]) { const T* s = &a[0]; std::size_t i = 0; while (*s++ && i < N) i++; return i; } inline void convert(const wide_string_type& in, std::string& out) { #ifdef NANODBC_USE_BOOST_CONVERT using boost::locale::conv::utf_to_utf; out = utf_to_utf(in.c_str(), in.c_str() + in.size()); #else // Workaround for confirmed bug in VS2015. See: // https://connect.microsoft.com/VisualStudio/Feedback/Details/1403302 // https://social.msdn.microsoft.com/Forums/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481 #if defined(_MSC_VER) && (_MSC_VER == 1900) auto p = reinterpret_cast(in.data()); out = std::wstring_convert, unsigned short>().to_bytes( p, p + in.size()); #else out = std::wstring_convert, wide_char_t>().to_bytes(in); #endif #endif } #ifdef NANODBC_USE_UNICODE inline void convert(const std::string& in, wide_string_type& out) { #ifdef NANODBC_USE_BOOST_CONVERT using boost::locale::conv::utf_to_utf; out = utf_to_utf(in.c_str(), in.c_str() + in.size()); // Workaround for confirmed bug in VS2015. See: // https://connect.microsoft.com/VisualStudio/Feedback/Details/1403302 // https://social.msdn.microsoft.com/Forums/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481 #elif defined(_MSC_VER) && (_MSC_VER == 1900) auto s = std::wstring_convert, unsigned short>().from_bytes(in); auto p = reinterpret_cast(s.data()); out.assign(p, p + s.size()); #else out = std::wstring_convert, wide_char_t>().from_bytes(in); #endif } inline void convert(const wide_string_type& in, wide_string_type& out) { out = in; } #else inline void convert(const std::string& in, std::string& out) { out = in; } #endif // Attempts to get the most recent ODBC error as a string. // Always returns std::string, even in unicode mode. inline std::string recent_error(SQLHANDLE handle, SQLSMALLINT handle_type, long& native, std::string& state) { nanodbc::string_type result; std::string rvalue; std::vector sql_message(SQL_MAX_MESSAGE_LENGTH); sql_message[0] = '\0'; SQLINTEGER i = 1; SQLINTEGER native_error; SQLSMALLINT total_bytes; NANODBC_SQLCHAR sql_state[6]; RETCODE rc; do { NANODBC_CALL_RC( NANODBC_FUNC(SQLGetDiagRec), rc, handle_type, handle, (SQLSMALLINT)i, sql_state, &native_error, 0, 0, &total_bytes); if (success(rc) && total_bytes > 0) sql_message.resize(total_bytes + 1); if (rc == SQL_NO_DATA) break; NANODBC_CALL_RC( NANODBC_FUNC(SQLGetDiagRec), rc, handle_type, handle, (SQLSMALLINT)i, sql_state, &native_error, sql_message.data(), (SQLSMALLINT)sql_message.size(), &total_bytes); if (!success(rc)) { convert(result, rvalue); return rvalue; } if (!result.empty()) result += ' '; result += nanodbc::string_type(sql_message.begin(), sql_message.end()); i++; // NOTE: unixODBC using PostgreSQL and SQLite drivers crash if you call SQLGetDiagRec() // more than once. So as a (terrible but the best possible) workaround just exit // this loop early on non-Windows systems. #ifndef _MSC_VER break; #endif } while (rc != SQL_NO_DATA); convert(result, rvalue); state = std::string(&sql_state[0], &sql_state[arrlen(sql_state) - 1]); native = native_error; std::string status = state; status += ": "; status += rvalue; // some drivers insert \0 into error messages for unknown reasons using std::replace; replace(status.begin(), status.end(), '\0', ' '); return status; } } // namespace namespace nanodbc { type_incompatible_error::type_incompatible_error() : std::runtime_error("type incompatible") { } const char* type_incompatible_error::what() const NANODBC_NOEXCEPT { return std::runtime_error::what(); } null_access_error::null_access_error() : std::runtime_error("null access") { } const char* null_access_error::what() const NANODBC_NOEXCEPT { return std::runtime_error::what(); } index_range_error::index_range_error() : std::runtime_error("index out of range") { } const char* index_range_error::what() const NANODBC_NOEXCEPT { return std::runtime_error::what(); } programming_error::programming_error(const std::string& info) : std::runtime_error(info.c_str()) { } const char* programming_error::what() const NANODBC_NOEXCEPT { return std::runtime_error::what(); } database_error::database_error(void* handle, short handle_type, const std::string& info) : std::runtime_error(info) , native_error(0) , sql_state("00000") { message = std::string(std::runtime_error::what()) + recent_error(handle, handle_type, native_error, sql_state); } const char* database_error::what() const NANODBC_NOEXCEPT { return message.c_str(); } long database_error::native() const NANODBC_NOEXCEPT { return native_error; } const std::string database_error::state() const NANODBC_NOEXCEPT { return sql_state; } } // namespace nanodbc // Throwing exceptions using NANODBC_THROW_DATABASE_ERROR enables file name // and line numbers to be inserted into the error message. Useful for debugging. #define NANODBC_THROW_DATABASE_ERROR(handle, handle_type) \ throw nanodbc::database_error( \ handle, handle_type, __FILE__ ":" NANODBC_STRINGIZE(__LINE__) ": ") /**/ // clang-format off // 8888888b. 888 d8b 888 // 888 "Y88b 888 Y8P 888 // 888 888 888 888 // 888 888 .d88b. 888888 8888b. 888 888 .d8888b // 888 888 d8P Y8b 888 "88b 888 888 88K // 888 888 88888888 888 .d888888 888 888 "Y8888b. // 888 .d88P Y8b. Y88b. 888 888 888 888 X88 // 8888888P" "Y8888 "Y888 "Y888888 888 888 88888P' // MARK: Details - // clang-format on #if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_STMT_EVENT) && \ defined(SQL_API_SQLCOMPLETEASYNC) #define NANODBC_DO_ASYNC_IMPL #endif namespace { using namespace std; // if int64_t is in std namespace (in c++11) template using is_integral8 = std::integral_constant< bool, std::is_integral::value && sizeof(T) == 1 && !std::is_same::value>; template using is_integral16 = std::integral_constant< bool, std::is_integral::value && sizeof(T) == 2 && !std::is_same::value>; template using is_integral32 = std::integral_constant< bool, std::is_integral::value && sizeof(T) == 4 && !std::is_same::value>; template using is_integral64 = std::integral_constant::value && sizeof(T) == 8>; // A utility for calculating the ctype from the given type T. // I essentially create a lookup table based on the MSDN ODBC documentation. // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms714556(v=vs.85).aspx template struct sql_ctype { }; template <> struct sql_ctype { static const SQLSMALLINT value = SQL_C_BINARY; }; template struct sql_ctype< T, typename std::enable_if::value && std::is_signed::value>::type> { static const SQLSMALLINT value = SQL_C_SSHORT; }; template struct sql_ctype< T, typename std::enable_if::value && std::is_unsigned::value>::type> { static const SQLSMALLINT value = SQL_C_USHORT; }; template struct sql_ctype< T, typename std::enable_if::value && std::is_signed::value>::type> { static const SQLSMALLINT value = SQL_C_SLONG; }; template struct sql_ctype< T, typename std::enable_if::value && std::is_unsigned::value>::type> { static const SQLSMALLINT value = SQL_C_ULONG; }; template struct sql_ctype< T, typename std::enable_if::value && std::is_signed::value>::type> { static const SQLSMALLINT value = SQL_C_SBIGINT; }; template struct sql_ctype< T, typename std::enable_if::value && std::is_unsigned::value>::type> { static const SQLSMALLINT value = SQL_C_UBIGINT; }; template <> struct sql_ctype { static const SQLSMALLINT value = SQL_C_FLOAT; }; template <> struct sql_ctype { static const SQLSMALLINT value = SQL_C_DOUBLE; }; template <> struct sql_ctype { #ifdef NANODBC_USE_UNICODE static const SQLSMALLINT value = SQL_C_WCHAR; #else static const SQLSMALLINT value = SQL_C_CHAR; #endif }; template <> struct sql_ctype { #ifdef NANODBC_USE_UNICODE static const SQLSMALLINT value = SQL_C_WCHAR; #else static const SQLSMALLINT value = SQL_C_CHAR; #endif }; template <> struct sql_ctype { static const SQLSMALLINT value = SQL_C_DATE; }; template <> struct sql_ctype { static const SQLSMALLINT value = SQL_C_TIME; }; template <> struct sql_ctype { static const SQLSMALLINT value = SQL_C_TIMESTAMP; }; // Encapsulates resources needed for column binding. class bound_column { public: bound_column(const bound_column& rhs) = delete; bound_column& operator=(bound_column rhs) = delete; bound_column() : name_() , column_(0) , sqltype_(0) , sqlsize_(0) , scale_(0) , ctype_(0) , clen_(0) , blob_(false) , cbdata_(0) , pdata_(0) { } ~bound_column() { delete[] cbdata_; delete[] pdata_; } public: nanodbc::string_type name_; short column_; SQLSMALLINT sqltype_; SQLULEN sqlsize_; SQLSMALLINT scale_; SQLSMALLINT ctype_; SQLULEN clen_; bool blob_; nanodbc::null_type* cbdata_; char* pdata_; }; // Encapsulates properties of statement parameter. // Parameter corresponds to parameter marker associated with a prepared SQL statement. struct bound_parameter { bound_parameter() = default; SQLUSMALLINT index_ = 0; // Zero-based index of parameter marker SQLSMALLINT iotype_ = 0; // Input/Output type of parameter SQLSMALLINT type_ = 0; // SQL data type of parameter SQLULEN size_ = 0; // SQL data size of column or expression inbytes or characters SQLSMALLINT scale_ = 0; // decimal digits of column or expression }; // Encapsulates properties of buffer with data values bound to statement parameter. template struct bound_buffer { bound_buffer() = default; bound_buffer(T const* values, std::size_t size, std::size_t value_size = 0) : values_(values) , size_(size) , value_size_(value_size) { } T const* values_ = nullptr; // Pointer to buffer for parameter's data std::size_t size_ = 0; // Number of values (1 or length of array) std::size_t value_size_ = 0; // Size of single value (max size). Zero, if ignored. }; // Allocates the native ODBC handles. inline void allocate_environment_handle(SQLHENV& env) { RETCODE rc; NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV); try { NANODBC_CALL_RC( SQLSetEnvAttr, rc, env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)NANODBC_ODBC_VERSION, SQL_IS_UINTEGER); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV); } catch (...) { NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env); throw; } } inline void allocate_handle(SQLHENV& env, SQLHDBC& conn) { allocate_environment_handle(env); try { NANODBC_ASSERT(env); RETCODE rc; NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_DBC, env, &conn); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV); } catch (...) { NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env); throw; } } } // namespace // clang-format off // .d8888b. 888 d8b 8888888 888 // d88P Y88b 888 Y8P 888 888 // 888 888 888 888 888 // 888 .d88b. 88888b. 88888b. .d88b. .d8888b 888888 888 .d88b. 88888b. 888 88888b.d88b. 88888b. 888 // 888 d88""88b 888 "88b 888 "88b d8P Y8b d88P" 888 888 d88""88b 888 "88b 888 888 "888 "88b 888 "88b 888 // 888 888 888 888 888 888 888 888 88888888 888 888 888 888 888 888 888 888 888 888 888 888 888 888 // Y88b d88P Y88..88P 888 888 888 888 Y8b. Y88b. Y88b. 888 Y88..88P 888 888 888 888 888 888 888 d88P 888 // "Y8888P" "Y88P" 888 888 888 888 "Y8888 "Y8888P "Y888 888 "Y88P" 888 888 8888888 888 888 888 88888P" 888 // 888 // 888 // 888 // MARK: Connection Impl - // clang-format on namespace nanodbc { class connection::connection_impl { public: connection_impl(const connection_impl&) = delete; connection_impl& operator=(const connection_impl&) = delete; connection_impl() : env_(0) , conn_(0) , connected_(false) , transactions_(0) , rollback_(false) { allocate_handle(env_, conn_); } connection_impl( const string_type& dsn, const string_type& user, const string_type& pass, long timeout) : env_(0) , conn_(0) , connected_(false) , transactions_(0) , rollback_(false) { allocate_handle(env_, conn_); try { connect(dsn, user, pass, timeout); } catch (...) { NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_DBC, conn_); NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env_); throw; } } connection_impl(const string_type& connection_string, long timeout) : env_(0) , conn_(0) , connected_(false) , transactions_(0) , rollback_(false) { allocate_handle(env_, conn_); try { connect(connection_string, timeout); } catch (...) { NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_DBC, conn_); NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env_); throw; } } ~connection_impl() NANODBC_NOEXCEPT { try { disconnect(); } catch (...) { // ignore exceptions thrown during disconnect } NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_DBC, conn_); NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env_); } #if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_DBC_EVENT) void enable_async(void* event_handle) { RETCODE rc; NANODBC_CALL_RC( SQLSetConnectAttr, rc, conn_, SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_ON, SQL_IS_INTEGER); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); NANODBC_CALL_RC( SQLSetConnectAttr, rc, conn_, SQL_ATTR_ASYNC_DBC_EVENT, event_handle, SQL_IS_POINTER); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); } void async_complete() { RETCODE rc, arc; NANODBC_CALL_RC(SQLCompleteAsync, rc, SQL_HANDLE_DBC, conn_, &arc); if (!success(rc) || !success(arc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); connected_ = true; NANODBC_CALL_RC( SQLSetConnectAttr, rc, conn_, SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_OFF, SQL_IS_INTEGER); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); } #endif // !NANODBC_DISABLE_ASYNC && SQL_ATTR_ASYNC_DBC_EVENT RETCODE connect( const string_type& dsn, const string_type& user, const string_type& pass, long timeout, void* event_handle = nullptr) { disconnect(); RETCODE rc; NANODBC_CALL_RC(SQLFreeHandle, rc, SQL_HANDLE_DBC, conn_); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_DBC, env_, &conn_); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(env_, SQL_HANDLE_ENV); NANODBC_CALL_RC( SQLSetConnectAttr, rc, conn_, SQL_LOGIN_TIMEOUT, (SQLPOINTER)(std::intptr_t)timeout, 0); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); #if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_DBC_EVENT) if (event_handle != nullptr) enable_async(event_handle); #endif NANODBC_CALL_RC( NANODBC_FUNC(SQLConnect), rc, conn_, (NANODBC_SQLCHAR*)dsn.c_str(), SQL_NTS, !user.empty() ? (NANODBC_SQLCHAR*)user.c_str() : 0, SQL_NTS, !pass.empty() ? (NANODBC_SQLCHAR*)pass.c_str() : 0, SQL_NTS); if (!success(rc) && (event_handle == nullptr || rc != SQL_STILL_EXECUTING)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); connected_ = success(rc); return rc; } RETCODE connect(const string_type& connection_string, long timeout, void* event_handle = nullptr) { disconnect(); RETCODE rc; NANODBC_CALL_RC(SQLFreeHandle, rc, SQL_HANDLE_DBC, conn_); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_DBC, env_, &conn_); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(env_, SQL_HANDLE_ENV); NANODBC_CALL_RC( SQLSetConnectAttr, rc, conn_, SQL_LOGIN_TIMEOUT, (SQLPOINTER)(std::intptr_t)timeout, 0); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); #if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_DBC_EVENT) if (event_handle != nullptr) enable_async(event_handle); #endif NANODBC_CALL_RC( NANODBC_FUNC(SQLDriverConnect), rc, conn_, 0, (NANODBC_SQLCHAR*)connection_string.c_str(), SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_NOPROMPT); if (!success(rc) && (event_handle == nullptr || rc != SQL_STILL_EXECUTING)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); connected_ = success(rc); return rc; } bool connected() const { return connected_; } void disconnect() { if (connected()) { RETCODE rc; NANODBC_CALL_RC(SQLDisconnect, rc, conn_); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); } connected_ = false; } std::size_t transactions() const { return transactions_; } void* native_dbc_handle() const { return conn_; } void* native_env_handle() const { return env_; } template T get_info(short info_type) const { return get_info_impl(info_type); } string_type dbms_name() const; string_type dbms_version() const; string_type driver_name() const; string_type database_name() const; string_type catalog_name() const { NANODBC_SQLCHAR name[SQL_MAX_OPTION_STRING_LENGTH] = {0}; SQLINTEGER length(0); RETCODE rc; NANODBC_CALL_RC( NANODBC_FUNC(SQLGetConnectAttr), rc, conn_, SQL_ATTR_CURRENT_CATALOG, name, sizeof(name) / sizeof(NANODBC_SQLCHAR), &length); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); return string_type(&name[0], &name[strarrlen(name)]); } std::size_t ref_transaction() { return ++transactions_; } std::size_t unref_transaction() { if (transactions_ > 0) --transactions_; return transactions_; } bool rollback() const { return rollback_; } void rollback(bool onoff) { rollback_ = onoff; } private: template T get_info_impl(short info_type) const; HENV env_; HDBC conn_; bool connected_; std::size_t transactions_; bool rollback_; // if true, this connection is marked for eventual transaction rollback }; template T connection::connection_impl::get_info_impl(short info_type) const { T value; RETCODE rc; NANODBC_CALL_RC(NANODBC_FUNC(SQLGetInfo), rc, conn_, info_type, &value, 0, nullptr); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); return value; } template <> string_type connection::connection_impl::get_info_impl(short info_type) const { NANODBC_SQLCHAR value[1024] = {0}; SQLSMALLINT length(0); RETCODE rc; NANODBC_CALL_RC( NANODBC_FUNC(SQLGetInfo), rc, conn_, info_type, value, sizeof(value) / sizeof(NANODBC_SQLCHAR), &length); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); return string_type(&value[0], &value[strarrlen(value)]); } string_type connection::connection_impl::dbms_name() const { return get_info(SQL_DBMS_NAME); } string_type connection::connection_impl::dbms_version() const { return get_info(SQL_DBMS_VER); } string_type connection::connection_impl::driver_name() const { return get_info(SQL_DRIVER_NAME); } string_type connection::connection_impl::database_name() const { return get_info(SQL_DATABASE_NAME); } template string_type connection::get_info(short info_type) const; template unsigned short connection::get_info(short info_type) const; template uint32_t connection::get_info(short info_type) const; template uint64_t connection::get_info(short info_type) const; } // namespace nanodbc // clang-format off // 88888888888 888 d8b 8888888 888 // 888 888 Y8P 888 888 // 888 888 888 888 // 888 888d888 8888b. 88888b. .d8888b 8888b. .d8888b 888888 888 .d88b. 88888b. 888 88888b.d88b. 88888b. 888 // 888 888P" "88b 888 "88b 88K "88b d88P" 888 888 d88""88b 888 "88b 888 888 "888 "88b 888 "88b 888 // 888 888 .d888888 888 888 "Y8888b. .d888888 888 888 888 888 888 888 888 888 888 888 888 888 888 888 // 888 888 888 888 888 888 X88 888 888 Y88b. Y88b. 888 Y88..88P 888 888 888 888 888 888 888 d88P 888 // 888 888 "Y888888 888 888 88888P' "Y888888 "Y8888P "Y888 888 "Y88P" 888 888 8888888 888 888 888 88888P" 888 // 888 // 888 // 888 // MARK: Transaction Impl - // clang-format on namespace nanodbc { class transaction::transaction_impl { public: transaction_impl(const transaction_impl&) = delete; transaction_impl& operator=(const transaction_impl&) = delete; transaction_impl(const class connection& conn) : conn_(conn) , committed_(false) { if (conn_.transactions() == 0 && conn_.connected()) { RETCODE rc; NANODBC_CALL_RC( SQLSetConnectAttr, rc, conn_.native_dbc_handle(), SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_.native_dbc_handle(), SQL_HANDLE_DBC); } conn_.ref_transaction(); } ~transaction_impl() NANODBC_NOEXCEPT { if (!committed_) { conn_.rollback(true); conn_.unref_transaction(); } if (conn_.transactions() == 0 && conn_.connected()) { if (conn_.rollback()) { NANODBC_CALL(SQLEndTran, SQL_HANDLE_DBC, conn_.native_dbc_handle(), SQL_ROLLBACK); conn_.rollback(false); } NANODBC_CALL( SQLSetConnectAttr, conn_.native_dbc_handle(), SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_UINTEGER); } } void commit() { if (committed_) return; committed_ = true; if (conn_.unref_transaction() == 0 && conn_.connected()) { RETCODE rc; NANODBC_CALL_RC(SQLEndTran, rc, SQL_HANDLE_DBC, conn_.native_dbc_handle(), SQL_COMMIT); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_.native_dbc_handle(), SQL_HANDLE_DBC); } } void rollback() NANODBC_NOEXCEPT { if (committed_) return; conn_.rollback(true); } class connection& connection() { return conn_; } const class connection& connection() const { return conn_; } private: class connection conn_; bool committed_; }; } // namespace nanodbc // clang-format off // .d8888b. 888 888 888 8888888 888 // d88P Y88b 888 888 888 888 888 // Y88b. 888 888 888 888 888 // "Y888b. 888888 8888b. 888888 .d88b. 88888b.d88b. .d88b. 88888b. 888888 888 88888b.d88b. 88888b. 888 // "Y88b. 888 "88b 888 d8P Y8b 888 "888 "88b d8P Y8b 888 "88b 888 888 888 "888 "88b 888 "88b 888 // "888 888 .d888888 888 88888888 888 888 888 88888888 888 888 888 888 888 888 888 888 888 888 // Y88b d88P Y88b. 888 888 Y88b. Y8b. 888 888 888 Y8b. 888 888 Y88b. 888 888 888 888 888 d88P 888 // "Y8888P" "Y888 "Y888888 "Y888 "Y8888 888 888 888 "Y8888 888 888 "Y888 8888888 888 888 888 88888P" 888 // 888 // 888 // 888 // MARK: Statement Impl - // clang-format on namespace nanodbc { class statement::statement_impl { public: statement_impl(const statement_impl&) = delete; statement_impl& operator=(const statement_impl&) = delete; statement_impl() : stmt_(0) , open_(false) , conn_() , bind_len_or_null_() #if defined(NANODBC_DO_ASYNC_IMPL) , async_(false) , async_enabled_(false) , async_event_(nullptr) #endif { } statement_impl(class connection& conn) : stmt_(0) , open_(false) , conn_() , bind_len_or_null_() , string_data_() , binary_data_() #if defined(NANODBC_DO_ASYNC_IMPL) , async_(false) , async_enabled_(false) , async_event_(nullptr) #endif { open(conn); } statement_impl(class connection& conn, const string_type& query, long timeout) : stmt_(0) , open_(false) , conn_() , bind_len_or_null_() , string_data_() , binary_data_() #if defined(NANODBC_DO_ASYNC_IMPL) , async_(false) , async_enabled_(false) , async_event_(nullptr) #endif { prepare(conn, query, timeout); } ~statement_impl() NANODBC_NOEXCEPT { if (open() && connected()) { NANODBC_CALL(SQLCancel, stmt_); reset_parameters(); NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_STMT, stmt_); } } void open(class connection& conn) { close(); RETCODE rc; NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_STMT, conn.native_dbc_handle(), &stmt_); open_ = success(rc); if (!open_) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); conn_ = conn; } bool open() const { return open_; } bool connected() const { return conn_.connected(); } const class connection& connection() const { return conn_; } class connection& connection() { return conn_; } void* native_statement_handle() const { return stmt_; } void close() { if (open() && connected()) { RETCODE rc; NANODBC_CALL_RC(SQLCancel, rc, stmt_); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); reset_parameters(); NANODBC_CALL_RC(SQLFreeHandle, rc, SQL_HANDLE_STMT, stmt_); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } open_ = false; stmt_ = 0; } void cancel() { RETCODE rc; NANODBC_CALL_RC(SQLCancel, rc, stmt_); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } void prepare(class connection& conn, const string_type& query, long timeout) { open(conn); prepare(query, timeout); } RETCODE prepare(const string_type& query, long timeout, void* event_handle = nullptr) { if (!open()) throw programming_error("statement has no associated open connection"); #if defined(NANODBC_DO_ASYNC_IMPL) if (event_handle == nullptr) disable_async(); else enable_async(event_handle); #endif RETCODE rc; NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)(std::intptr_t)SQL_CURSOR_DYNAMIC, 0); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); NANODBC_CALL_RC( NANODBC_FUNC(SQLPrepare), rc, stmt_, (NANODBC_SQLCHAR*)query.c_str(), (SQLINTEGER)query.size()); if (!success(rc) && rc != SQL_STILL_EXECUTING) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); this->timeout(timeout); return rc; } void timeout(long timeout) { RETCODE rc; NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_, SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER)(std::intptr_t)timeout, 0); // some drivers don't support timeout for statements, // so only raise the error if a non-default timeout was requested. if (!success(rc) && (timeout != 0)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } #if defined(NANODBC_DO_ASYNC_IMPL) void enable_async(void* event_handle) { RETCODE rc; if (!async_enabled_) { NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_, SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_ON, SQL_IS_INTEGER); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); async_enabled_ = true; } if (async_event_ != event_handle) { NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_, SQL_ATTR_ASYNC_STMT_EVENT, event_handle, SQL_IS_POINTER); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); async_event_ = event_handle; } } void disable_async() const { if (async_enabled_) { RETCODE rc; NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_, SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_OFF, SQL_IS_INTEGER); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); async_enabled_ = false; } } bool async_helper(RETCODE rc) { if (rc == SQL_STILL_EXECUTING) { async_ = true; return true; } else if (success(rc)) { async_ = false; return false; } else { NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } } bool async_prepare(const string_type& query, void* event_handle, long timeout) { return async_helper(prepare(query, timeout, event_handle)); } bool async_execute_direct( class connection& conn, void* event_handle, const string_type& query, long batch_operations, long timeout, statement& statement) { return async_helper( just_execute_direct(conn, query, batch_operations, timeout, statement, event_handle)); } bool async_execute(void* event_handle, long batch_operations, long timeout, statement& statement) { return async_helper(just_execute(batch_operations, timeout, statement, event_handle)); } void call_complete_async() { if (async_) { RETCODE rc, arc; NANODBC_CALL_RC(SQLCompleteAsync, rc, SQL_HANDLE_STMT, stmt_, &arc); if (!success(rc) || !success(arc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } } result complete_execute(long batch_operations, statement& statement) { call_complete_async(); return result(statement, batch_operations); } void complete_prepare() { call_complete_async(); } #endif result execute_direct( class connection& conn, const string_type& query, long batch_operations, long timeout, statement& statement) { #ifdef NANODBC_HANDLE_NODATA_BUG const RETCODE rc = just_execute_direct(conn, query, batch_operations, timeout, statement); if (rc == SQL_NO_DATA) return result(); #else just_execute_direct(conn, query, batch_operations, timeout, statement); #endif return result(statement, batch_operations); } RETCODE just_execute_direct( class connection& conn, const string_type& query, long batch_operations, long timeout, statement&, // statement void* event_handle = nullptr) { open(conn); #if defined(NANODBC_DO_ASYNC_IMPL) if (event_handle == nullptr) disable_async(); else enable_async(event_handle); #endif RETCODE rc; NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)(std::intptr_t)SQL_CURSOR_DYNAMIC, 0); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)(std::intptr_t)batch_operations, 0); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); this->timeout(timeout); NANODBC_CALL_RC( NANODBC_FUNC(SQLExecDirect), rc, stmt_, (NANODBC_SQLCHAR*)query.c_str(), SQL_NTS); if (!success(rc) && rc != SQL_NO_DATA && rc != SQL_STILL_EXECUTING) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); return rc; } result execute(long batch_operations, long timeout, statement& statement) { #ifdef NANODBC_HANDLE_NODATA_BUG const RETCODE rc = just_execute(batch_operations, timeout, statement); if (rc == SQL_NO_DATA) return result(); #else just_execute(batch_operations, timeout, statement); #endif return result(statement, batch_operations); } RETCODE just_execute( long batch_operations, long timeout, statement& /*statement*/, void* event_handle = nullptr) { RETCODE rc; if (open()) { // The ODBC cursor must be closed before subsequent executions, as described // here // http://msdn.microsoft.com/en-us/library/windows/desktop/ms713584%28v=vs.85%29.aspx // // However, we don't necessarily want to call SQLCloseCursor() because that // will cause an invalid cursor state in the case that no cursor is currently open. // A better solution is to use SQLFreeStmt() with the SQL_CLOSE option, which has // the same effect without the undesired limitations. NANODBC_CALL_RC(SQLFreeStmt, rc, stmt_, SQL_CLOSE); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } #if defined(NANODBC_DO_ASYNC_IMPL) if (event_handle == nullptr) disable_async(); else enable_async(event_handle); #endif NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)(std::intptr_t)batch_operations, 0); if (!success(rc) && rc != SQL_NO_DATA) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); this->timeout(timeout); NANODBC_CALL_RC(SQLExecute, rc, stmt_); if (!success(rc) && rc != SQL_NO_DATA && rc != SQL_STILL_EXECUTING) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); return rc; } result procedure_columns( const string_type& catalog, const string_type& schema, const string_type& procedure, const string_type& column, statement& statement) { if (!open()) throw programming_error("statement has no associated open connection"); #if defined(NANODBC_DO_ASYNC_IMPL) disable_async(); #endif RETCODE rc; NANODBC_CALL_RC( NANODBC_FUNC(SQLProcedureColumns), rc, stmt_, (NANODBC_SQLCHAR*)(catalog.empty() ? nullptr : catalog.c_str()), (catalog.empty() ? 0 : SQL_NTS), (NANODBC_SQLCHAR*)(schema.empty() ? nullptr : schema.c_str()), (schema.empty() ? 0 : SQL_NTS), (NANODBC_SQLCHAR*)procedure.c_str(), SQL_NTS, (NANODBC_SQLCHAR*)(column.empty() ? nullptr : column.c_str()), (column.empty() ? 0 : SQL_NTS)); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); return result(statement, 1); } long affected_rows() const { SQLLEN rows; RETCODE rc; NANODBC_CALL_RC(SQLRowCount, rc, stmt_, &rows); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); NANODBC_ASSERT(rows <= static_cast(std::numeric_limits::max())); return static_cast(rows); } short columns() const { SQLSMALLINT cols; RETCODE rc; #if defined(NANODBC_DO_ASYNC_IMPL) disable_async(); #endif NANODBC_CALL_RC(SQLNumResultCols, rc, stmt_, &cols); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); return cols; } void reset_parameters() NANODBC_NOEXCEPT { NANODBC_CALL(SQLFreeStmt, stmt_, SQL_RESET_PARAMS); } short parameters() const { SQLSMALLINT params; RETCODE rc; #if defined(NANODBC_DO_ASYNC_IMPL) disable_async(); #endif NANODBC_CALL_RC(SQLNumParams, rc, stmt_, ¶ms); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); return params; } unsigned long parameter_size(short param_index) const { RETCODE rc; SQLSMALLINT data_type; SQLSMALLINT nullable; SQLULEN parameter_size; #if defined(NANODBC_DO_ASYNC_IMPL) disable_async(); #endif NANODBC_CALL_RC( SQLDescribeParam, rc, stmt_, param_index + 1, &data_type, ¶meter_size, 0, &nullable); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); NANODBC_ASSERT( parameter_size <= static_cast(std::numeric_limits::max())); return static_cast(parameter_size); } static SQLSMALLINT param_type_from_direction(param_direction direction) { switch (direction) { case PARAM_IN: return SQL_PARAM_INPUT; break; case PARAM_OUT: return SQL_PARAM_OUTPUT; break; case PARAM_INOUT: return SQL_PARAM_INPUT_OUTPUT; break; case PARAM_RETURN: return SQL_PARAM_OUTPUT; break; default: NANODBC_ASSERT(false); throw programming_error("unrecognized param_direction value"); } } // initializes bind_len_or_null_ and gets information for bind void prepare_bind( short param_index, std::size_t batch_size, param_direction direction, bound_parameter& param) { NANODBC_ASSERT(param_index >= 0); #if defined(NANODBC_DO_ASYNC_IMPL) disable_async(); #endif RETCODE rc; SQLSMALLINT nullable; // unused NANODBC_CALL_RC( SQLDescribeParam, rc, stmt_, param_index + 1, ¶m.type_, ¶m.size_, ¶m.scale_, &nullable); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); param.index_ = param_index; param.iotype_ = param_type_from_direction(direction); if (!bind_len_or_null_.count(param_index)) bind_len_or_null_[param_index] = std::vector(); std::vector().swap(bind_len_or_null_[param_index]); // ODBC weirdness: this must be at least 8 elements in size const std::size_t indicator_size = batch_size > 8 ? batch_size : 8; bind_len_or_null_[param_index].reserve(indicator_size); bind_len_or_null_[param_index].assign(indicator_size, SQL_NULL_DATA); NANODBC_ASSERT(param.index_ == param_index); NANODBC_ASSERT(param.iotype_ > 0); } // calls actual ODBC bind parameter function template void bind_parameter(bound_parameter const& param, bound_buffer& buffer) { NANODBC_ASSERT(param.index_ >= 0); auto const buffer_size = buffer.value_size_ > 0 ? buffer.value_size_ : param.size_; RETCODE rc; NANODBC_CALL_RC( SQLBindParameter, rc, stmt_, // handle param.index_ + 1, // parameter number param.iotype_, // input or output type sql_ctype::value, // value type param.type_, // parameter type param.size_, // column size ignored for many types, but needed for strings param.scale_, // decimal digits (SQLPOINTER)buffer.values_, // parameter value buffer_size, // buffer length bind_len_or_null_[param.index_].data()); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } template void bind( param_direction direction, short param_index, T const* values, std::size_t batch_size, bool const* nulls = nullptr, T const* null_sentry = nullptr); // handles multiple binary values void bind( param_direction direction, short param_index, std::vector> const& values, bool const* nulls = nullptr, uint8_t const* null_sentry = nullptr) { std::size_t batch_size = values.size(); bound_parameter param; prepare_bind(param_index, batch_size, direction, param); size_t max_length = 0; for (std::size_t i = 0; i < batch_size; ++i) { max_length = std::max(values[i].size(), max_length); } binary_data_[param_index] = std::vector(batch_size * max_length, 0); for (std::size_t i = 0; i < batch_size; ++i) { std::copy( values[i].begin(), values[i].end(), binary_data_[param_index].data() + (i * max_length)); } if (null_sentry) { for (std::size_t i = 0; i < batch_size; ++i) if (!std::equal(values[i].begin(), values[i].end(), null_sentry)) { bind_len_or_null_[param_index][i] = values[i].size(); } } else if (nulls) { for (std::size_t i = 0; i < batch_size; ++i) { if (!nulls[i]) bind_len_or_null_[param_index][i] = values[i].size(); // null terminated } } else { for (std::size_t i = 0; i < batch_size; ++i) { bind_len_or_null_[param_index][i] = values[i].size(); } } bound_buffer buffer(binary_data_[param_index].data(), batch_size, max_length); bind_parameter(param, buffer); } void bind_strings( param_direction direction, short param_index, string_type::value_type const* values, std::size_t value_size, std::size_t batch_size, bool const* nulls = nullptr, string_type::value_type const* null_sentry = nullptr); void bind_strings( param_direction direction, short param_index, std::vector const& values, bool const* nulls = nullptr, string_type::value_type const* null_sentry = nullptr); // handles multiple null values void bind_null(short param_index, std::size_t batch_size) { bound_parameter param; prepare_bind(param_index, batch_size, PARAM_IN, param); RETCODE rc; NANODBC_CALL_RC( SQLBindParameter, rc, stmt_, param.index_ + 1, // parameter number param.iotype_, // input or output typ, SQL_C_CHAR, param.type_, // parameter type param.size_, // column size ignored for many types, but needed for string, 0, // decimal digits nullptr, // null value 0, // buffe length bind_len_or_null_[param.index_].data()); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } // comparator for null sentry values template bool equals(const T& lhs, const T& rhs) { return lhs == rhs; } private: HSTMT stmt_; bool open_; class connection conn_; std::map> bind_len_or_null_; std::map> string_data_; std::map> binary_data_; #if defined(NANODBC_DO_ASYNC_IMPL) bool async_; // true if statement is currently in SQL_STILL_EXECUTING mode mutable bool async_enabled_; // true if statement currently has SQL_ATTR_ASYNC_ENABLE = // SQL_ASYNC_ENABLE_ON void* async_event_; // currently active event handle for async notifications #endif }; // Supports code like: query.bind(0, std_string.c_str()) // In this case, we need to pass nullptr to the final parameter of SQLBindParameter(). template <> void statement::statement_impl::bind_parameter( bound_parameter const& param, bound_buffer& buffer) { auto const buffer_size = buffer.value_size_ > 0 ? buffer.value_size_ : param.size_; RETCODE rc; NANODBC_CALL_RC( SQLBindParameter, rc, stmt_, // handle param.index_ + 1, // parameter number param.iotype_, // input or output type sql_ctype::value, // value type param.type_, // parameter type param.size_, // column size ignored for many types, but needed for strings param.scale_, // decimal digits (SQLPOINTER)buffer.values_, // parameter value buffer_size, // buffer length (buffer.size_ <= 1 ? nullptr : bind_len_or_null_[param.index_].data())); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } template void statement::statement_impl::bind( param_direction direction, short param_index, T const* values, std::size_t batch_size, bool const* nulls /*= nullptr*/, T const* null_sentry /*= nullptr*/) { bound_parameter param; prepare_bind(param_index, batch_size, direction, param); if (nulls || null_sentry) { for (std::size_t i = 0; i < batch_size; ++i) if ((null_sentry && !equals(values[i], *null_sentry)) || (nulls && !nulls[i]) || !nulls) bind_len_or_null_[param_index][i] = param.size_; } else { for (std::size_t i = 0; i < batch_size; ++i) bind_len_or_null_[param_index][i] = param.size_; } bound_buffer buffer(values, batch_size); bind_parameter(param, buffer); } void statement::statement_impl::bind_strings( param_direction direction, short param_index, std::vector const& values, bool const* nulls /*= nullptr*/, string_type::value_type const* null_sentry /*= nullptr*/) { size_t const batch_size = values.size(); bound_parameter param; prepare_bind(param_index, batch_size, direction, param); size_t max_length = 0; for (std::size_t i = 0; i < batch_size; ++i) { max_length = std::max(values[i].length(), max_length); } // add space for null terminator ++max_length; string_data_[param_index] = std::vector(batch_size * max_length, 0); for (std::size_t i = 0; i < batch_size; ++i) { std::copy( values[i].begin(), values[i].end(), string_data_[param_index].data() + (i * max_length)); } bind_strings( direction, param_index, string_data_[param_index].data(), max_length, batch_size, nulls, null_sentry); } void statement::statement_impl::bind_strings( param_direction direction, short param_index, string_type::value_type const* values, std::size_t value_size, std::size_t batch_size, bool const* nulls /*= nullptr*/, string_type::value_type const* null_sentry /*= nullptr*/) { bound_parameter param; prepare_bind(param_index, batch_size, direction, param); if (null_sentry) { for (std::size_t i = 0; i < batch_size; ++i) { const string_type s_lhs(values + i * value_size, values + (i + 1) * value_size); const string_type s_rhs(null_sentry); #if NANODBC_USE_UNICODE std::string narrow_lhs; narrow_lhs.reserve(s_lhs.size()); convert(s_lhs, narrow_lhs); std::string narrow_rhs; narrow_rhs.reserve(s_rhs.size()); convert(s_rhs, narrow_rhs); if (std::strncmp(narrow_lhs.c_str(), narrow_rhs.c_str(), value_size) != 0) bind_len_or_null_[param_index][i] = SQL_NTS; #else if (std::strncmp(s_lhs.c_str(), s_rhs.c_str(), value_size) != 0) bind_len_or_null_[param_index][i] = SQL_NTS; #endif } } else if (nulls) { for (std::size_t i = 0; i < batch_size; ++i) { if (!nulls[i]) bind_len_or_null_[param_index][i] = SQL_NTS; // null terminated } } else { for (std::size_t i = 0; i < batch_size; ++i) { bind_len_or_null_[param_index][i] = SQL_NTS; } } auto const buffer_length = value_size * sizeof(string_type::value_type); bound_buffer buffer(values, batch_size, buffer_length); bind_parameter(param, buffer); } template <> bool statement::statement_impl::equals(const date& lhs, const date& rhs) { return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day; } template <> bool statement::statement_impl::equals(const time& lhs, const time& rhs) { return lhs.hour == rhs.hour && lhs.min == rhs.min && lhs.sec == rhs.sec; } template <> bool statement::statement_impl::equals(const timestamp& lhs, const timestamp& rhs) { return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day && lhs.hour == rhs.hour && lhs.min == rhs.min && lhs.sec == rhs.sec && lhs.fract == rhs.fract; } } // namespace nanodbc // clang-format off // 8888888b. 888 888 8888888 888 // 888 Y88b 888 888 888 888 // 888 888 888 888 888 888 // 888 d88P .d88b. .d8888b 888 888 888 888888 888 88888b.d88b. 88888b. 888 // 8888888P" d8P Y8b 88K 888 888 888 888 888 888 "888 "88b 888 "88b 888 // 888 T88b 88888888 "Y8888b. 888 888 888 888 888 888 888 888 888 888 888 // 888 T88b Y8b. X88 Y88b 888 888 Y88b. 888 888 888 888 888 d88P 888 // 888 T88b "Y8888 88888P' "Y88888 888 "Y888 8888888 888 888 888 88888P" 888 // 888 // 888 // 888 // MARK: Result Impl - // clang-format on namespace nanodbc { class result::result_impl { public: result_impl(const result_impl&) = delete; result_impl& operator=(const result_impl&) = delete; result_impl(statement stmt, long rowset_size) : stmt_(stmt) , rowset_size_(rowset_size) , row_count_(0) , bound_columns_(0) , bound_columns_size_(0) , rowset_position_(0) , bound_columns_by_name_() , at_end_(false) #if defined(NANODBC_DO_ASYNC_IMPL) , async_(false) #endif { RETCODE rc; NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_.native_statement_handle(), SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)(std::intptr_t)rowset_size_, 0); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); NANODBC_CALL_RC( SQLSetStmtAttr, rc, stmt_.native_statement_handle(), SQL_ATTR_ROWS_FETCHED_PTR, &row_count_, 0); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); auto_bind(); } ~result_impl() NANODBC_NOEXCEPT { cleanup_bound_columns(); } void* native_statement_handle() const { return stmt_.native_statement_handle(); } long rowset_size() const { return rowset_size_; } long affected_rows() const { return stmt_.affected_rows(); } long rows() const NANODBC_NOEXCEPT { NANODBC_ASSERT(row_count_ <= static_cast(std::numeric_limits::max())); return static_cast(row_count_); } short columns() const { return stmt_.columns(); } bool first() { rowset_position_ = 0; return fetch(0, SQL_FETCH_FIRST); } bool last() { rowset_position_ = 0; return fetch(0, SQL_FETCH_LAST); } bool next(void* event_handle = nullptr) { if (rows() && ++rowset_position_ < rowset_size_) return rowset_position_ < rows(); rowset_position_ = 0; return fetch(0, SQL_FETCH_NEXT, event_handle); } #if defined(NANODBC_DO_ASYNC_IMPL) bool async_next(void* event_handle) { async_ = next(event_handle); return async_; } bool complete_next() { if (async_) { RETCODE rc, arc; NANODBC_CALL_RC( SQLCompleteAsync, rc, SQL_HANDLE_STMT, stmt_.native_statement_handle(), &arc); if (arc == SQL_NO_DATA) { at_end_ = true; return false; } if (!success(rc) || !success(arc)) NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); async_ = false; } return !at_end_; } #endif bool prior() { if (rows() && --rowset_position_ >= 0) return true; rowset_position_ = 0; return fetch(0, SQL_FETCH_PRIOR); } bool move(long row) { rowset_position_ = 0; return fetch(row, SQL_FETCH_ABSOLUTE); } bool skip(long rows) { rowset_position_ += rows; if (this->rows() && rowset_position_ < rowset_size_) return rowset_position_ < this->rows(); rowset_position_ = 0; return fetch(rows, SQL_FETCH_RELATIVE); } unsigned long position() const { SQLULEN pos = 0; // necessary to initialize to 0 RETCODE rc; NANODBC_CALL_RC( SQLGetStmtAttr, rc, stmt_.native_statement_handle(), SQL_ATTR_ROW_NUMBER, &pos, SQL_IS_UINTEGER, 0); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); // MSDN (https://msdn.microsoft.com/en-us/library/ms712631.aspx): // If the number of the current row cannot be determined or // there is no current row, the driver returns 0. // Otherwise, valid row number is returned, starting at 1. // // NOTE: We try to address incorrect implementation in some drivers (e.g. SQLite ODBC) // which instead of 0 return SQL_ROW_NUMBER_UNKNOWN(-2) . if (pos == 0 || pos == static_cast(SQL_ROW_NUMBER_UNKNOWN)) return 0; NANODBC_ASSERT(pos <= static_cast(std::numeric_limits::max())); return static_cast(pos) + rowset_position_; } bool at_end() const NANODBC_NOEXCEPT { if (at_end_) return true; SQLULEN pos = 0; // necessary to initialize to 0 RETCODE rc; NANODBC_CALL_RC( SQLGetStmtAttr, rc, stmt_.native_statement_handle(), SQL_ATTR_ROW_NUMBER, &pos, SQL_IS_UINTEGER, 0); return (!success(rc) || rows() < 0 || pos - 1 > static_cast(rows())); } bool is_null(short column) const { if (column >= bound_columns_size_) throw index_range_error(); bound_column& col = bound_columns_[column]; if (rowset_position_ >= rows()) throw index_range_error(); return col.cbdata_[rowset_position_] == SQL_NULL_DATA; } bool is_null(const string_type& column_name) const { const short column = this->column(column_name); return is_null(column); } short column(const string_type& column_name) const { typedef std::map::const_iterator iter; iter i = bound_columns_by_name_.find(column_name); if (i == bound_columns_by_name_.end()) throw index_range_error(); return i->second->column_; } string_type column_name(short column) const { if (column >= bound_columns_size_) throw index_range_error(); return bound_columns_[column].name_; } long column_size(short column) const { if (column >= bound_columns_size_) throw index_range_error(); bound_column& col = bound_columns_[column]; NANODBC_ASSERT(col.sqlsize_ <= static_cast(std::numeric_limits::max())); return static_cast(col.sqlsize_); } int column_size(const string_type& column_name) const { const short column = this->column(column_name); return column_size(column); } int column_decimal_digits(short column) const { if (column >= bound_columns_size_) throw index_range_error(); bound_column& col = bound_columns_[column]; return col.scale_; } int column_decimal_digits(const string_type& column_name) const { const short column = this->column(column_name); bound_column& col = bound_columns_[column]; return col.scale_; } int column_datatype(short column) const { if (column >= bound_columns_size_) throw index_range_error(); bound_column& col = bound_columns_[column]; return col.sqltype_; } int column_datatype(const string_type& column_name) const { const short column = this->column(column_name); bound_column& col = bound_columns_[column]; return col.sqltype_; } int column_c_datatype(short column) const { if (column >= bound_columns_size_) throw index_range_error(); bound_column& col = bound_columns_[column]; return col.ctype_; } int column_c_datatype(const string_type& column_name) const { const short column = this->column(column_name); bound_column& col = bound_columns_[column]; return col.ctype_; } bool next_result() { RETCODE rc; #if defined(NANODBC_DO_ASYNC_IMPL) stmt_.disable_async(); #endif NANODBC_CALL_RC(SQLMoreResults, rc, stmt_.native_statement_handle()); if (rc == SQL_NO_DATA) return false; if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); auto_bind(); return true; } template void get_ref(short column, T& result) const { if (column >= bound_columns_size_) throw index_range_error(); if (is_null(column)) throw null_access_error(); get_ref_impl(column, result); } template void get_ref(short column, const T& fallback, T& result) const { if (column >= bound_columns_size_) throw index_range_error(); if (is_null(column)) { result = fallback; return; } get_ref_impl(column, result); } template void get_ref(const string_type& column_name, T& result) const { const short column = this->column(column_name); if (is_null(column)) throw null_access_error(); get_ref_impl(column, result); } template void get_ref(const string_type& column_name, const T& fallback, T& result) const { const short column = this->column(column_name); if (is_null(column)) { result = fallback; return; } get_ref_impl(column, result); } template T get(short column) const { T result; get_ref(column, result); return result; } template T get(short column, const T& fallback) const { T result; get_ref(column, fallback, result); return result; } template T get(const string_type& column_name) const { T result; get_ref(column_name, result); return result; } template T get(const string_type& column_name, const T& fallback) const { T result; get_ref(column_name, fallback, result); return result; } private: template void get_ref_impl(short column, T& result) const; void before_move() NANODBC_NOEXCEPT { for (short i = 0; i < bound_columns_size_; ++i) { bound_column& col = bound_columns_[i]; for (long j = 0; j < rowset_size_; ++j) col.cbdata_[j] = 0; if (col.blob_ && col.pdata_) release_bound_resources(i); } } void release_bound_resources(short column) NANODBC_NOEXCEPT { NANODBC_ASSERT(column < bound_columns_size_); bound_column& col = bound_columns_[column]; delete[] col.pdata_; col.pdata_ = 0; col.clen_ = 0; } void cleanup_bound_columns() NANODBC_NOEXCEPT { before_move(); delete[] bound_columns_; bound_columns_ = nullptr; bound_columns_size_ = 0; bound_columns_by_name_.clear(); } // If event_handle is specified, fetch returns true iff the statement is still executing bool fetch(long rows, SQLUSMALLINT orientation, void* event_handle = nullptr) { before_move(); #if defined(NANODBC_DO_ASYNC_IMPL) if (event_handle == nullptr) stmt_.disable_async(); else stmt_.enable_async(event_handle); #endif // !NANODBC_DISABLE_ASYNC && SQL_ATTR_ASYNC_STMT_EVENT && SQL_API_SQLCOMPLETEASYNC RETCODE rc; NANODBC_CALL_RC(SQLFetchScroll, rc, stmt_.native_statement_handle(), orientation, rows); if (rc == SQL_NO_DATA) { at_end_ = true; return false; } #if defined(NANODBC_DO_ASYNC_IMPL) if (event_handle != nullptr) return rc == SQL_STILL_EXECUTING; #endif if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); return true; } void auto_bind() { cleanup_bound_columns(); const short n_columns = columns(); if (n_columns < 1) return; NANODBC_ASSERT(!bound_columns_); NANODBC_ASSERT(!bound_columns_size_); bound_columns_ = new bound_column[n_columns]; bound_columns_size_ = n_columns; RETCODE rc; NANODBC_SQLCHAR column_name[1024]; SQLSMALLINT sqltype, scale, nullable, len; SQLULEN sqlsize; #if defined(NANODBC_DO_ASYNC_IMPL) stmt_.disable_async(); #endif for (SQLSMALLINT i = 0; i < n_columns; ++i) { NANODBC_CALL_RC( NANODBC_FUNC(SQLDescribeCol), rc, stmt_.native_statement_handle(), i + 1, (NANODBC_SQLCHAR*)column_name, sizeof(column_name) / sizeof(NANODBC_SQLCHAR), &len, &sqltype, &sqlsize, &scale, &nullable); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); // Adjust the sqlsize parameter in case of "unlimited" data (varchar(max), // nvarchar(max)). bool is_blob = false; if (sqlsize == 0) { switch (sqltype) { case SQL_VARCHAR: case SQL_WVARCHAR: { // Divide in half, due to sqlsize being 32-bit in Win32 (and 64-bit in x64) // sqlsize = std::numeric_limits::max() / 2 - 1; is_blob = true; } } } bound_column& col = bound_columns_[i]; col.name_ = reinterpret_cast(column_name); col.column_ = i; col.sqltype_ = sqltype; col.sqlsize_ = sqlsize; col.scale_ = scale; bound_columns_by_name_[col.name_] = &col; using namespace std; // if int64_t is in std namespace (in c++11) switch (col.sqltype_) { case SQL_BIT: case SQL_TINYINT: case SQL_SMALLINT: case SQL_INTEGER: case SQL_BIGINT: col.ctype_ = SQL_C_SBIGINT; col.clen_ = sizeof(int64_t); break; case SQL_DOUBLE: case SQL_FLOAT: case SQL_DECIMAL: case SQL_REAL: case SQL_NUMERIC: col.ctype_ = SQL_C_DOUBLE; col.clen_ = sizeof(double); break; case SQL_DATE: case SQL_TYPE_DATE: col.ctype_ = SQL_C_DATE; col.clen_ = sizeof(date); break; case SQL_TIME: case SQL_TYPE_TIME: case SQL_SS_TIME2: col.ctype_ = SQL_C_TIME; col.clen_ = sizeof(time); break; case SQL_TIMESTAMP: case SQL_TYPE_TIMESTAMP: case SQL_SS_TIMESTAMPOFFSET: col.ctype_ = SQL_C_TIMESTAMP; col.clen_ = sizeof(timestamp); break; case SQL_CHAR: case SQL_VARCHAR: case SQL_NVARCHAR: col.ctype_ = SQL_C_CHAR; col.clen_ = (col.sqlsize_ + 1) * sizeof(SQLCHAR); if (is_blob) { col.clen_ = 0; col.blob_ = true; } break; case SQL_WCHAR: case SQL_WVARCHAR: col.ctype_ = SQL_C_WCHAR; col.clen_ = (col.sqlsize_ + 1) * sizeof(SQLWCHAR); if (is_blob) { col.clen_ = 0; col.blob_ = true; } break; case SQL_LONGVARCHAR: col.ctype_ = SQL_C_CHAR; col.blob_ = true; col.clen_ = 0; break; case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: case SQL_SS_UDT: // MSDN: Essentially, UDT is a varbinary type with additional metadata. col.ctype_ = SQL_C_BINARY; col.blob_ = true; col.clen_ = 0; break; default: col.ctype_ = sql_ctype::value; col.clen_ = 128; break; } } for (SQLSMALLINT i = 0; i < n_columns; ++i) { bound_column& col = bound_columns_[i]; col.cbdata_ = new null_type[rowset_size_]; if (col.blob_) { NANODBC_CALL_RC( SQLBindCol, rc, stmt_.native_statement_handle(), i + 1, col.ctype_, 0, 0, col.cbdata_); if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); } else { col.pdata_ = new char[rowset_size_ * col.clen_]; NANODBC_CALL_RC( SQLBindCol, rc, stmt_.native_statement_handle(), i + 1, // ColumnNumber col.ctype_, // TargetType col.pdata_, // TargetValuePtr col.clen_, // BufferLength col.cbdata_); // StrLen_or_Ind if (!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); } } } private: statement stmt_; const long rowset_size_; SQLULEN row_count_; bound_column* bound_columns_; short bound_columns_size_; long rowset_position_; std::map bound_columns_by_name_; bool at_end_; #if defined(NANODBC_DO_ASYNC_IMPL) bool async_; // true if statement is currently in SQL_STILL_EXECUTING mode #endif }; template <> inline void result::result_impl::get_ref_impl(short column, date& result) const { bound_column& col = bound_columns_[column]; switch (col.ctype_) { case SQL_C_DATE: result = *reinterpret_cast(col.pdata_ + rowset_position_ * col.clen_); return; case SQL_C_TIMESTAMP: { timestamp stamp = *reinterpret_cast(col.pdata_ + rowset_position_ * col.clen_); date d = {stamp.year, stamp.month, stamp.day}; result = d; return; } } throw type_incompatible_error(); } template <> inline void result::result_impl::get_ref_impl