diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index ea5db40904..b425e99c5d 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -463,6 +463,7 @@ target_link_libraries( common pybind11::embed compoundfilereader pcm_settings + nanodbc # for now; maybe hoist out of common ${Boost_LIBRARIES} ${CURL_LIBRARIES} ${wxWidgets_LIBRARIES} diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 7195e440ac..e8515ba44e 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -32,6 +32,7 @@ add_subdirectory( gzip-hpp ) add_subdirectory( lemon ) add_subdirectory( libcontext ) add_subdirectory( markdown2html ) +add_subdirectory( nanodbc ) add_subdirectory( nanosvg ) add_subdirectory( other_math ) add_subdirectory( rtree ) diff --git a/thirdparty/nanodbc/CMakeLists.txt b/thirdparty/nanodbc/CMakeLists.txt new file mode 100644 index 0000000000..7caec92736 --- /dev/null +++ b/thirdparty/nanodbc/CMakeLists.txt @@ -0,0 +1,209 @@ +cmake_minimum_required(VERSION 3.0.0) +project(nanodbc CXX) + +# nanodbc specific options +option(NANODBC_DISABLE_ASYNC "Disable async features entirely" OFF) +option(NANODBC_DISABLE_LIBCXX "Do not use libc++, if available." OFF) +option(NANODBC_ENABLE_BOOST "Use Boost for Unicode string convertions (requires Boost.Locale)" ON) +option(NANODBC_ENABLE_UNICODE "Enable Unicode support" ON) +option(NANODBC_ENABLE_WORKAROUND_NODATA "Enable SQL_NO_DATA workaround (see Issue #33)" OFF) + +######################################## +## nanodbc version +######################################## +file(STRINGS VERSION.txt NANODBC_VERSION REGEX "[0-9]+\\.[0-9]+\\.[0-9]+") +string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" NANODBC_VERSION_MAJOR "${NANODBC_VERSION}") +string(REGEX REPLACE "^[0-9]+\\.([0-9])+\\.[0-9]+" "\\1" NANODBC_VERSION_MINOR "${NANODBC_VERSION}") +string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" NANODBC_VERSION_PATCH "${NANODBC_VERSION}") +message(STATUS "nanodbc version: ${NANODBC_VERSION}") + +######################################## +## require and enable C++0x/11/14 +######################################## +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +message(STATUS "nanodbc compile: C++${CMAKE_CXX_STANDARD}") + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wnarrowing -Werror") + include(CheckCXXCompilerFlag) + + if(NOT NANODBC_DISABLE_LIBCXX) + check_cxx_compiler_flag("-stdlib=libc++" CXX_SUPPORTS_STDLIB) + if(CXX_SUPPORTS_STDLIB) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -stdlib=libc++") + endif() + set(NANODBC_DISABLE_LIBCXX ${NANODBC_DISABLE_LIBCXX} CACHE BOOL "Do not use libc++, if available." FORCE) + endif() + message(STATUS "nanodbc build: Disable linking libc++ - ${NANODBC_DISABLE_LIBCXX}") +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel") + string(REGEX REPLACE "[/-]W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + if (NOT (CMAKE_VERSION VERSION_LESS 3.6.0)) # Compiler features for Intel in CMake 3.6+ + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Qstd=c++17") + endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /QaxCORE-AVX2") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:precise") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O3") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Qipo") +elseif(MSVC) + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + if(MSVC_VERSION LESS 1700) + message(FATAL_ERROR, "nanodbc requires C++11-compliant compiler") + endif() +endif() + +######################################## +## nanodbc features +######################################## +IF(NOT DEFINED NANODBC_ODBC_VERSION) + message(STATUS "nanodbc feature: ODBC Version Override - OFF") +else() + message(STATUS "nanodbc feature: ODBC Version Override - ${NANODBC_ODBC_VERSION}") + add_definitions(-DNANODBC_ODBC_VERSION=${NANODBC_ODBC_VERSION}) +endif() + +if(NANODBC_DISABLE_ASYNC) + add_definitions(-DNANODBC_DISABLE_ASYNC) +endif() +message(STATUS "nanodbc feature: Disable async features - ${NANODBC_DISABLE_ASYNC}") + +if(NANODBC_ENABLE_UNICODE) + add_definitions(-DNANODBC_ENABLE_UNICODE) + if(MSVC) + # Sets "Use Unicode Character Set" property in Visual Studio projects + add_definitions(-DUNICODE -D_UNICODE) + endif() +endif() +message(STATUS "nanodbc feature: Enable Unicode - ${NANODBC_ENABLE_UNICODE}") + +if(NANODBC_ENABLE_BOOST) + add_definitions(-DNANODBC_ENABLE_BOOST) +endif() +message(STATUS "nanodbc feature: Enable Boost - ${NANODBC_ENABLE_BOOST}") + +if(NANODBC_ENABLE_WORKAROUND_NODATA) + add_definitions(-DNANODBC_ENABLE_WORKAROUND_NODATA) +endif() +message(STATUS "nanodbc feature: Enable SQL_NO_DATA bug workaround - ${NANODBC_ENABLE_WORKAROUND_NODATA}") + +######################################## +## find unixODBC or iODBC config binary +######################################## +if(UNIX) + # Try to find unixODBC first via odbc_config program. + find_program(ODBC_CONFIG odbc_config + PATHS $ENV{ODBC_PATH}/bin /usr/bin /usr/local/bin) + if(ODBC_CONFIG) + message(STATUS "nanodbc build: ODBC on Unix - unixODBC") + set(ODBCLIB odbc) + execute_process(COMMAND ${ODBC_CONFIG} --include-prefix + OUTPUT_VARIABLE ODBC_INCLUDE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) + set(ODBC_CFLAGS "-I${ODBC_INCLUDE_DIR}") + set(CMAKE_FLAGS "${CMAKE_FLAGS} ${ODBC_CFLAGS}") + execute_process(COMMAND ${ODBC_CONFIG} --libs + OUTPUT_VARIABLE ODBC_LINK_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + + # Fallback to finding unixODBC via install paths + if(NOT ODBC_CONFIG) + find_path(UnixODBC_INCLUDE_DIR uodbc_stats.h + /usr/include + /usr/local/include + /usr/include/odbc + /usr/local/include/odbc + /usr/include/libodbc + /usr/local/include/libodbc) + if(UnixODBC_INCLUDE_DIR) + set(ODBC_CONFIG 1) + message(STATUS "nanodbc build: ODBC on Unix - unixODBC") + set(ODBCLIB odbc) + set(ODBC_CFLAGS "-I${UnixODBC_INCLUDE_DIR} -DHAVE_UNISTD_H -DHAVE_PWD_H -DHAVE_SYS_TYPES_H -DHAVE_LONG_LONG -DSIZEOF_LONG_INT=8") + endif() + endif() + + # Fallback to using iODBC + if(NOT ODBC_CONFIG) + find_program(ODBC_CONFIG iodbc-config + PATHS $ENV{ODBC_PATH}/bin /usr/bin /usr/local/bin) + if(ODBC_CONFIG) + message(STATUS "nanodbc build: ODBC on Unix - iODBC") + set(ODBCLIB iodbc) + execute_process(COMMAND ${ODBC_CONFIG} --cflags + OUTPUT_VARIABLE ODBC_CFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_FLAGS "${CMAKE_FLAGS} ${ODBC_CFLAGS}") + execute_process(COMMAND ${ODBC_CONFIG} --libs + OUTPUT_VARIABLE ODBC_LINK_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NANODBC_ENABLE_UNICODE) + add_definitions(-DNANODBC_USE_IODBC_WIDE_STRINGS) + endif() + endif() + endif() + + if(NOT ODBC_CONFIG) + message(FATAL_ERROR "can not find a suitable odbc driver manager") + endif() + + message(STATUS "ODBC compile flags: ${ODBC_CFLAGS}") + message(STATUS "ODBC link flags: ${ODBC_LINK_FLAGS}") +endif() + +######################################## +## find ODBC libraries to link +######################################## +if(UNIX) + set(ODBC_LIBRARIES ${ODBCLIB}) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${ODBC_LINK_FLAGS}") +elseif(MSVC OR CMAKE_CXX_COMPILER_ID MATCHES "Intel") + set(ODBC_LIBRARIES odbc32.lib odbccp32.lib Ws2_32.lib) +elseif(MINGW) + set(ODBC_LIBRARIES odbc32 odbccp32) +endif() + +######################################## +## find Boost if necessary +######################################## +if(NANODBC_ENABLE_BOOST) + set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_MULTITHREADED ON) + find_package(Boost COMPONENTS locale REQUIRED) + if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + link_directories(${CMAKE_BINARY_DIR}/lib ${Boost_LIBRARY_DIRS}) + else() + message(FATAL_ERROR "can not find boost") + endif() +endif() + +######################################## +## Mac OS X specifics for targets +######################################## +if(APPLE) + set(CMAKE_MACOSX_RPATH ON) + message(STATUS "Use rpaths on Mac OS X - ${CMAKE_MACOSX_RPATH}") + + # AppleClang complains of unused `-I/path/` arguments. + # These are harmless and can be safely ignored. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-command-line-argument") +endif() + +######################################## +## library target +######################################## +add_library(nanodbc nanodbc/nanodbc.cpp nanodbc/nanodbc.h) + +target_link_libraries(nanodbc ${Boost_LIBRARIES} ${ODBC_LIBRARIES}) + +target_include_directories(nanodbc PUBLIC + $ + $) # /include/nanodbc + +if(UNIX) + set_target_properties(nanodbc PROPERTIES + COMPILE_FLAGS "${ODBC_CFLAGS}" + LIBRARY_OUTPUT_DIRECTORY "lib") +endif() diff --git a/thirdparty/nanodbc/LICENSE b/thirdparty/nanodbc/LICENSE new file mode 100644 index 0000000000..ef26969f84 --- /dev/null +++ b/thirdparty/nanodbc/LICENSE @@ -0,0 +1,19 @@ +The MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/thirdparty/nanodbc/VERSION.txt b/thirdparty/nanodbc/VERSION.txt new file mode 100644 index 0000000000..edcfe40d19 --- /dev/null +++ b/thirdparty/nanodbc/VERSION.txt @@ -0,0 +1 @@ +2.14.0 diff --git a/thirdparty/nanodbc/nanodbc/nanodbc.cpp b/thirdparty/nanodbc/nanodbc/nanodbc.cpp new file mode 100644 index 0000000000..fb85598a40 --- /dev/null +++ b/thirdparty/nanodbc/nanodbc/nanodbc.cpp @@ -0,0 +1,5478 @@ +/// \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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __clang__ +#include +#endif + +// User may redefine NANODBC_ASSERT macro in nanodbc/nanodbc.h +#ifndef NANODBC_ASSERT +#include +#define NANODBC_ASSERT(expr) assert(expr) +#endif + +#ifdef NANODBC_ENABLE_BOOST +#include +#elif defined(__GNUC__) && (__GNUC__ < 5) +#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 +#if !defined(__MINGW32__) && !defined(NOMINMAX) +#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/nanodbc/nanodbc/issues/18 +#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 + +// SQL_SS_LENGTH_UNLIMITED is used to describe the max length of +// VARCHAR(max), VARBINARY(max), NVARCHAR(max), and XML columns +#ifndef SQL_SS_LENGTH_UNLIMITED +#define SQL_SS_LENGTH_UNLIMITED (0) +#endif + +// Max length of DBVARBINARY and DBVARCHAR, etc. +1 for zero byte +// MSDN: Large value data types are those that exceed the maximum row size of 8 KB +#define SQLSERVER_DBMAXCHAR (8000 + 1) + +// 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 + +// Import string types defined in header file, so we don't have to type nanodbc:: everywhere +using nanodbc::wide_char_t; +using nanodbc::wide_string; + +#ifdef NANODBC_ENABLE_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 +#define NANODBC_CODECVT_TYPE std::codecvt_utf8 +#else +#ifdef _MSC_VER +#define NANODBC_CODECVT_TYPE std::codecvt_utf8_utf16 +#else +#define NANODBC_CODECVT_TYPE std::codecvt_utf8_utf16 +#endif +#endif + +#if defined(_MSC_VER) +#ifndef NANODBC_ENABLE_UNICODE +// Disable unicode in sqlucode.h on Windows when NANODBC_ENABLE_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; +} + +#if __cpp_lib_nonmember_container_access >= 201411 || _MSC_VER +using std::size; +#else +template +constexpr std::size_t size(const T (&array)[N]) noexcept +{ + return N; +} +#endif + +template +inline std::size_t size(NANODBC_SQLCHAR const (&array)[N]) noexcept +{ + auto const n = std::char_traits::length(array); + NANODBC_ASSERT(n < N); + return n < N ? n : N - 1; +} + +template +inline void convert(T const* beg, size_t n, std::basic_string& out) +{ + out.assign(beg, n); +} + +inline void convert(wide_char_t const* beg, size_t n, std::string& out) +{ +#ifdef NANODBC_ENABLE_BOOST + using boost::locale::conv::utf_to_utf; + out = utf_to_utf(beg, beg + n); +#elif defined(_MSC_VER) && (_MSC_VER == 1900) + // 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/vs-2015-rc-linker-stdcodecvt-error + // Why static? http://stackoverflow.com/questions/26196686/utf8-utf16-codecvt-poor-performance + static thread_local std::wstring_convert, unsigned short> + converter; + out = converter.to_bytes( + reinterpret_cast(beg), + reinterpret_cast(beg + n)); +#else + static thread_local std::wstring_convert, wide_char_t> + converter; + out = converter.to_bytes(beg, beg + n); +#endif +} + +inline void convert(char const* beg, size_t n, wide_string& out) +{ +#ifdef NANODBC_ENABLE_BOOST + using boost::locale::conv::utf_to_utf; + out = utf_to_utf(beg, beg + n); +#elif defined(_MSC_VER) && (_MSC_VER == 1900) + // 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/vs-2015-rc-linker-stdcodecvt-error + // Why static? http://stackoverflow.com/questions/26196686/utf8-utf16-codecvt-poor-performance + static thread_local std::wstring_convert, unsigned short> + converter; + auto s = converter.from_bytes(beg, beg + n); + auto p = reinterpret_cast(s.data()); + out.assign(p, p + s.size()); +#else + static thread_local std::wstring_convert, wide_char_t> + converter; + out = converter.from_bytes(beg, beg + n); +#endif +} + +template +inline void convert(char const* beg, std::basic_string& out) +{ + convert(beg, std::strlen(beg), out); +} + +template +inline void convert(wchar_t const* beg, std::basic_string& out) +{ + convert(beg, std::wcslen(beg), out); +} + +template +inline void convert(std::basic_string&& in, std::basic_string& out) +{ + out.assign(in); +} + +template +inline void convert(std::basic_string const& in, std::basic_string& out) +{ + convert(in.data(), in.size(), out); +} + +// 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 result; + std::string rvalue; + std::vector sql_message(SQL_MAX_MESSAGE_LENGTH); + sql_message[0] = '\0'; + + SQLINTEGER i = 1; + SQLINTEGER native_error = 0; + SQLSMALLINT total_bytes = 0; + NANODBC_SQLCHAR sql_state[6] = {0}; + 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(static_cast(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(std::move(result), rvalue); + return rvalue; + } + + if (!result.empty()) + result += ' '; + + result += nanodbc::string(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(std::move(result), rvalue); + if (size(sql_state) > 0) + { + state.clear(); + state.reserve(size(sql_state) - 1); + for (std::size_t idx = 0; idx != size(sql_state) - 1; ++idx) + { + state.push_back(static_cast(sql_state[idx])); + } + } + + 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 + +#ifndef NANODBC_DISABLE_NANODBC_NAMESPACE_FOR_INTERNAL_TESTS +namespace nanodbc +{ + +type_incompatible_error::type_incompatible_error() + : std::runtime_error("type incompatible") +{ +} + +const char* type_incompatible_error::what() const noexcept +{ + return std::runtime_error::what(); +} + +null_access_error::null_access_error() + : std::runtime_error("null access") +{ +} + +const char* null_access_error::what() const 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 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 noexcept +{ + return std::runtime_error::what(); +} + +database_error::database_error(SQLHANDLE 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 noexcept +{ + return message.c_str(); +} + +long database_error::native() const noexcept +{ + return native_error; +} + +const std::string database_error::state() const 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. +#ifdef NANODBC_THROW_NO_SOURCE_LOCATION +#define NANODBC_THROW_DATABASE_ERROR(handle, handle_type) \ + throw nanodbc::database_error(handle, handle_type, "ODBC database error: ") /**/ +#else +#define NANODBC_THROW_DATABASE_ERROR(handle, handle_type) \ + throw nanodbc::database_error( \ + handle, handle_type, __FILE__ ":" NANODBC_STRINGIZE(__LINE__) ": ") /**/ +#endif + +// 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 +{ + static const SQLSMALLINT value = SQL_C_WCHAR; +}; + +template <> +struct sql_ctype +{ + static const SQLSMALLINT value = SQL_C_WCHAR; +}; + +template <> +struct sql_ctype +{ + static const SQLSMALLINT value = SQL_C_CHAR; +}; + +template <> +struct sql_ctype +{ + static const SQLSMALLINT value = SQL_C_CHAR; +}; + +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_(false) + { + } + + ~bound_column() + { + delete[] cbdata_; + delete[] pdata_; + } + +public: + nanodbc::string name_; + short column_; + SQLSMALLINT sqltype_; + SQLULEN sqlsize_; + SQLSMALLINT scale_; + SQLSMALLINT ctype_; + SQLULEN clen_; + bool blob_; + nanodbc::null_type* cbdata_; + char* pdata_; + bool bound_; +}; + +// Encapsulates properties of statement parameter. +// Parameter corresponds to parameter marker associated with a prepared SQL statement. +struct bound_parameter +{ + bound_parameter() = default; + + SQLULEN size_ = 0; // SQL data size of column or expression inbytes or characters + 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 + 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. +}; + +inline void deallocate_handle(SQLHANDLE& handle, short handle_type) +{ + if (!handle) + return; + + RETCODE rc; + NANODBC_CALL_RC(SQLFreeHandle, rc, handle_type, handle); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(handle, handle_type); + handle = nullptr; +} + +inline void allocate_env_handle(SQLHENV& env) +{ + if (env) + return; + + 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 (...) + { + deallocate_handle(env, SQL_HANDLE_ENV); + throw; + } +} + +inline void allocate_dbc_handle(SQLHDBC& conn, SQLHENV env) +{ + NANODBC_ASSERT(env); + if (conn) + return; + + try + { + RETCODE rc; + NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_DBC, env, &conn); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV); + } + catch (...) + { + deallocate_handle(conn, SQL_HANDLE_DBC); + 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_(nullptr) + , dbc_(nullptr) + , connected_(false) + , transactions_(0) + , rollback_(false) + { + } + + connection_impl(const string& dsn, const string& user, const string& pass, long timeout) + : env_(nullptr) + , dbc_(nullptr) + , connected_(false) + , transactions_(0) + , rollback_(false) + { + allocate(); + + try + { + connect(dsn, user, pass, timeout); + } + catch (...) + { + deallocate(); + throw; + } + } + + connection_impl(const string& connection_string, long timeout) + : env_(nullptr) + , dbc_(nullptr) + , connected_(false) + , transactions_(0) + , rollback_(false) + { + allocate(); + + try + { + connect(connection_string, timeout); + } + catch (...) + { + deallocate(); + throw; + } + } + + ~connection_impl() noexcept + { + try + { + disconnect(); + } + catch (...) + { + // ignore exceptions thrown during disconnect + } + deallocate(); + } + + void allocate() + { + allocate_env_handle(env_); + allocate_dbc_handle(dbc_, env_); + } + + void deallocate() + { + deallocate_handle(dbc_, SQL_HANDLE_DBC); + deallocate_handle(env_, SQL_HANDLE_ENV); + } + +#if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_DBC_EVENT) + void enable_async(void* event_handle) + { + NANODBC_ASSERT(dbc_); + + RETCODE rc; + NANODBC_CALL_RC( + SQLSetConnectAttr, + rc, + dbc_, + SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, + (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_ON, + SQL_IS_INTEGER); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, SQL_HANDLE_DBC); + + NANODBC_CALL_RC( + SQLSetConnectAttr, rc, dbc_, SQL_ATTR_ASYNC_DBC_EVENT, event_handle, SQL_IS_POINTER); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, SQL_HANDLE_DBC); + } + + void async_complete() + { + NANODBC_ASSERT(dbc_); + + RETCODE rc, arc; + NANODBC_CALL_RC(SQLCompleteAsync, rc, SQL_HANDLE_DBC, dbc_, &arc); + if (!success(rc) || !success(arc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, SQL_HANDLE_DBC); + + connected_ = true; + + NANODBC_CALL_RC( + SQLSetConnectAttr, + rc, + dbc_, + SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, + (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_OFF, + SQL_IS_INTEGER); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, SQL_HANDLE_DBC); + } +#endif // !NANODBC_DISABLE_ASYNC && SQL_ATTR_ASYNC_DBC_EVENT + + RETCODE connect( + const string& dsn, + const string& user, + const string& pass, + long timeout, + void* event_handle = nullptr) + { + allocate_env_handle(env_); + disconnect(); + + deallocate_handle(dbc_, SQL_HANDLE_DBC); + allocate_dbc_handle(dbc_, env_); + + RETCODE rc; + if (timeout != 0) + { + // Avoid to set the timeout to 0 (no timeout). + // This is a workaround for the Oracle ODBC Driver (11.1), as this + // operation is not supported by the Driver. + NANODBC_CALL_RC( + SQLSetConnectAttr, + rc, + dbc_, + SQL_LOGIN_TIMEOUT, + (SQLPOINTER)(std::intptr_t)timeout, + 0); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, 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, + dbc_, + (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(dbc_, SQL_HANDLE_DBC); + + connected_ = success(rc); + + return rc; + } + + RETCODE + connect(const string& connection_string, long timeout, void* event_handle = nullptr) + { + allocate_env_handle(env_); + disconnect(); + + deallocate_handle(dbc_, SQL_HANDLE_DBC); + allocate_dbc_handle(dbc_, env_); + + RETCODE rc; + if (timeout != 0) + { + // Avoid to set the timeout to 0 (no timeout). + // This is a workaround for the Oracle ODBC Driver (11.1), as this + // operation is not supported by the Driver. + NANODBC_CALL_RC( + SQLSetConnectAttr, + rc, + dbc_, + SQL_LOGIN_TIMEOUT, + (SQLPOINTER)(std::intptr_t)timeout, + 0); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, 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, + dbc_, + 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(dbc_, SQL_HANDLE_DBC); + + connected_ = success(rc); + + return rc; + } + + bool connected() const { return connected_; } + + void disconnect() + { + if (connected()) + { + RETCODE rc; + NANODBC_CALL_RC(SQLDisconnect, rc, dbc_); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, SQL_HANDLE_DBC); + } + connected_ = false; + } + + std::size_t transactions() const { return transactions_; } + + void* native_dbc_handle() const { return dbc_; } + + void* native_env_handle() const { return env_; } + + template + T get_info(short info_type) const + { + return get_info_impl(info_type); + } + string dbms_name() const; + + string dbms_version() const; + + string driver_name() const; + + string database_name() const; + + string catalog_name() const + { + NANODBC_SQLCHAR name[SQL_MAX_OPTION_STRING_LENGTH] = {0}; + SQLINTEGER length(0); + RETCODE rc; + NANODBC_CALL_RC( + NANODBC_FUNC(SQLGetConnectAttr), + rc, + dbc_, + SQL_ATTR_CURRENT_CATALOG, + name, + sizeof(name) / sizeof(NANODBC_SQLCHAR), + &length); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, SQL_HANDLE_DBC); + return string(&name[0], &name[size(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 dbc_; + 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, dbc_, info_type, &value, 0, nullptr); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, SQL_HANDLE_DBC); + return value; +} + +template <> +string 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, + dbc_, + info_type, + value, + sizeof(value) / sizeof(NANODBC_SQLCHAR), + &length); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(dbc_, SQL_HANDLE_DBC); + return string(&value[0], &value[size(value)]); +} + +string connection::connection_impl::dbms_name() const +{ + return get_info(SQL_DBMS_NAME); +} + +string connection::connection_impl::dbms_version() const +{ + return get_info(SQL_DBMS_VER); +} + +string connection::connection_impl::driver_name() const +{ + return get_info(SQL_DRIVER_NAME); +} + +string connection::connection_impl::database_name() const +{ + return get_info(SQL_DATABASE_NAME); +} + +template string 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() 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() 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_() + , wide_string_data_() + , 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& query, long timeout) + : stmt_(0) + , open_(false) + , conn_() + , bind_len_or_null_() + , wide_string_data_() + , 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() noexcept + { + if (open() && connected()) + { + NANODBC_CALL(SQLCancel, stmt_); + reset_parameters(); + deallocate_handle(stmt_, SQL_HANDLE_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_) + { + if (!stmt_) + NANODBC_THROW_DATABASE_ERROR(conn.native_dbc_handle(), SQL_HANDLE_DBC); + else + 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(); + deallocate_handle(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& query, long timeout) + { + open(conn); + prepare(query, timeout); + } + + RETCODE prepare(const string& 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( + 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& 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& 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& query, + long batch_operations, + long timeout, + statement& statement) + { +#ifdef NANODBC_ENABLE_WORKAROUND_NODATA + 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& 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_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_ENABLE_WORKAROUND_NODATA + 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& catalog, + const string& schema, + const string& procedure, + const string& 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() noexcept + { + param_descr_data_.clear(); + 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 + + if (!param_descr_data_.count(param_index)) + { + 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); + } + else + { + param.type_ = param_descr_data_[param_index].type_; + param.size_ = param_descr_data_[param_index].size_; + param.scale_ = param_descr_data_[param_index].scale_; + } + + 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 ::value, int>::type = 0> + void bind_parameter(bound_parameter const& param, bound_buffer& buffer) + { + NANODBC_ASSERT(buffer.value_size_ > 0 || param.size_ > 0); + + auto value_size{buffer.value_size_}; + if (value_size == 0) + value_size = param.size_; + + auto param_size{param.size_}; + if (value_size > param_size) + { + // Parameter size reported by SQLDescribeParam for Large Objects: + // - For SQL VARBINARY(MAX), it is Zero which actually means SQL_SS_LENGTH_UNLIMITED. + // - For SQL UDT (eg. GEOMETRY), it may be driver-specific max limit (eg. SQL Server is + // DBMAXCHAR=8000 bytes). + // See MSDN for details + // https://docs.microsoft.com/en-us/sql/relational-databases/native-client/odbc/large-clr-user-defined-types-odbc + // + // If bound value is larger than parameter size, we force SQL_SS_LENGTH_UNLIMITED. + param_size = SQL_SS_LENGTH_UNLIMITED; + } + + 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 + value_size, // buffer length + bind_len_or_null_[param.index_].data()); + + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); + } + + // 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 ::value, int>::type = 0> + void 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 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); + } + + template > + void bind_strings( + param_direction direction, + short param_index, + T const* values, + std::size_t value_size, + std::size_t batch_size, + bool const* nulls = nullptr, + T const* null_sentry = nullptr); + + template > + void bind_strings( + param_direction direction, + short param_index, + std::vector const& values, + bool const* nulls = nullptr, + typename T::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); + } + + void describe_parameters( + const std::vector& idx, + const std::vector& type, + const std::vector& size, + const std::vector& scale) + { + + if (idx.size() != type.size() || idx.size() != size.size() || idx.size() != scale.size()) + throw programming_error("parameter description arrays are of different size"); + + for (std::size_t i = 0; i < idx.size(); ++i) + { + param_descr_data_[idx[i]].type_ = static_cast(type[i]); + param_descr_data_[idx[i]].size_ = static_cast(size[i]); + param_descr_data_[idx[i]].scale_ = static_cast(scale[i]); + param_descr_data_[idx[i]].index_ = static_cast(i); + param_descr_data_[idx[i]].iotype_ = PARAM_IN; // not used + } + } + + // comparator for null sentry values + template + bool equals(const T& lhs, const T& rhs) + { + return lhs == rhs; + } + + template + std::vector& get_bound_string_data(short param_index); + +private: + HSTMT stmt_; + bool open_; + class connection conn_; + std::map> bind_len_or_null_; + std::map> wide_string_data_; + std::map> string_data_; + std::map> binary_data_; + std::map param_descr_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 +}; + +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); +} + +template +void statement::statement_impl::bind_strings( + param_direction direction, + short param_index, + std::vector const& values, + bool const* nulls /*= nullptr*/, + typename T::value_type const* null_sentry /*= nullptr*/) +{ + using string_vector = std::vector; + string_vector& string_data = get_bound_string_data(param_index); + + 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 = string_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.data() + (i * max_length)); + } + bind_strings( + direction, param_index, string_data.data(), max_length, batch_size, nulls, null_sentry); +} + +template +void statement::statement_impl::bind_strings( + param_direction direction, + short param_index, + T const* values, + std::size_t value_size, + 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 (null_sentry) + { + for (std::size_t i = 0; i < batch_size; ++i) + { + const std::basic_string s_lhs( + values + i * value_size, values + (i + 1) * value_size); + const std::basic_string s_rhs(null_sentry); + if (!equals(s_lhs, s_rhs)) + bind_len_or_null_[param_index][i] = SQL_NTS; + } + } + 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(T); + bound_buffer buffer(values, batch_size, buffer_length); + bind_parameter(param, buffer); +} + +template <> +bool statement::statement_impl::equals(const std::string& lhs, const std::string& rhs) +{ + return std::strncmp(lhs.c_str(), rhs.c_str(), lhs.size()) == 0; +} + +template <> +bool statement::statement_impl::equals(const wide_string& lhs, const wide_string& rhs) +{ + // e6059ff3a79062f83256b9d1d3c9c8368798781e + // Functions like `swprintf()`, `wcsftime()`, `wcsncmp()` can not be used + // with `u16string` types. Instead, prefers to narrow unicode string to + // work with them, and then widen them after work has been completed. + std::string narrow_lhs; + narrow_lhs.reserve(lhs.size()); + convert(lhs, narrow_lhs); + std::string narrow_rhs; + narrow_rhs.reserve(rhs.size()); + convert(rhs, narrow_rhs); + return equals(narrow_lhs, narrow_rhs); +} + +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; +} + +template <> +std::vector& +statement::statement_impl::get_bound_string_data(short param_index) +{ + return wide_string_data_[param_index]; +} + +template <> +std::vector& +statement::statement_impl::get_bound_string_data(short param_index) +{ + return string_data_[param_index]; +} + +} // 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() 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(); } + + bool has_affected_rows() const { return stmt_.affected_rows() != -1; } + + long rows() const 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 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 + { + throw_if_column_is_out_of_range(column); + bound_column& col = bound_columns_[column]; + if (rowset_position_ >= rows()) + throw index_range_error(); + return col.cbdata_[static_cast(rowset_position_)] == SQL_NULL_DATA; + } + + bool is_null(const string& column_name) const + { + const short column = this->column(column_name); + return is_null(column); + } + + bool is_bound(short column) const + { + throw_if_column_is_out_of_range(column); + bound_column& col = bound_columns_[column]; + return col.bound_; + } + + bool is_bound(const string& column_name) const + { + const short column = this->column(column_name); + return is_bound(column); + } + + short column(const string& 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 column_name(short column) const + { + throw_if_column_is_out_of_range(column); + return bound_columns_[column].name_; + } + + long column_size(short column) const + { + throw_if_column_is_out_of_range(column); + bound_column& col = bound_columns_[column]; + NANODBC_ASSERT(col.sqlsize_ <= static_cast(std::numeric_limits::max())); + return static_cast(col.sqlsize_); + } + + long column_size(const string& column_name) const + { + const short column = this->column(column_name); + return column_size(column); + } + + int column_decimal_digits(short column) const + { + throw_if_column_is_out_of_range(column); + bound_column& col = bound_columns_[column]; + return col.scale_; + } + + int column_decimal_digits(const string& 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 + { + throw_if_column_is_out_of_range(column); + bound_column& col = bound_columns_[column]; + return col.sqltype_; + } + + int column_datatype(const string& column_name) const + { + const short column = this->column(column_name); + bound_column& col = bound_columns_[column]; + return col.sqltype_; + } + + string column_datatype_name(short column) const + { + throw_if_column_is_out_of_range(column); + + NANODBC_SQLCHAR type_name[256] = {0}; + SQLSMALLINT len = 0; // total number of bytes + RETCODE rc; + NANODBC_CALL_RC( + NANODBC_FUNC(SQLColAttribute), + rc, + stmt_.native_statement_handle(), + column + 1, + SQL_DESC_TYPE_NAME, + type_name, + sizeof(type_name), + &len, + nullptr); + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); + + NANODBC_ASSERT(len % sizeof(NANODBC_SQLCHAR) == 0); + len = len / sizeof(NANODBC_SQLCHAR); + return string(type_name, type_name + len); + } + + string column_datatype_name(const string& column_name) const + { + return column_datatype_name(this->column(column_name)); + } + + int column_c_datatype(short column) const + { + throw_if_column_is_out_of_range(column); + bound_column& col = bound_columns_[column]; + return col.ctype_; + } + + int column_c_datatype(const string& 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; + } + + void unbind() + { + const short n_columns = columns(); + if (n_columns < 1) + return; + for (short i = 0; i < n_columns; ++i) + unbind(i); + } + + void unbind(short column) + { + RETCODE rc; + throw_if_column_is_out_of_range(column); + bound_column& col = bound_columns_[column]; + + if (is_bound(column)) + { + NANODBC_CALL_RC( + SQLBindCol, + rc, + stmt_.native_statement_handle(), + column + 1, + col.ctype_, + 0, + 0, + col.cbdata_); // Re-use existing cbdata_ buffer + if (!success(rc)) + NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT); + delete[] col.pdata_; + col.pdata_ = 0; + col.bound_ = false; + } + } + + void unbind(const string& column_name) + { + const short column = this->column(column_name); + unbind(column); + } + + template + void get_ref(short column, T& result) const + { + throw_if_column_is_out_of_range(column); + 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 + { + throw_if_column_is_out_of_range(column); + if (is_null(column)) + { + result = fallback; + return; + } + get_ref_impl(column, result); + } + + template + void get_ref(const string& 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& 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& column_name) const + { + T result; + get_ref(column_name, result); + return result; + } + + template + T get(const string& column_name, const T& fallback) const + { + T result; + get_ref(column_name, fallback, result); + return result; + } + +private: + template + std::unique_ptr> ensure_pdata(short column) const; + + template ::value, int>::type = 0> + void get_ref_impl(short column, T& result) const; + + template ::value, int>::type = 0> + void get_ref_impl(short column, T& result) const; + + template ::value, int>::type = 0> + void get_ref_from_string_column(short column, T& result) const; + + template ::value, int>::type = 0> + void get_ref_from_string_column(short column, T& result) const; + + void throw_if_column_is_out_of_range(short column) const + { + if ((column < 0) || (column >= bound_columns_size_)) + throw index_range_error(); + } + + void before_move() noexcept + { + for (short i = 0; i < bound_columns_size_; ++i) + { + bound_column& col = bound_columns_[i]; + for (std::size_t j = 0; j < static_cast(rowset_size_); ++j) + col.cbdata_[j] = 0; + if (col.blob_ && col.pdata_) + release_bound_resources(i); + } + } + + void release_bound_resources(short column) 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() 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 = 0, scale = 0, nullable = 0, len = 0; + SQLULEN sqlsize = 0; + +#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: + case SQL_SS_XML: + { + // 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_REAL: + col.ctype_ = SQL_C_DOUBLE; + col.clen_ = sizeof(double); + break; + case SQL_DECIMAL: + case SQL_NUMERIC: + col.ctype_ = SQL_C_CHAR; + // SQL column size defines number of digits without the decimal mark + // and without minus sign which may also occur. + // We need to adjust buffer length allow space for null-termination character + // as well as the fractional part separator and the minus sign. + col.clen_ = (col.sqlsize_ + 1 + 1 + 1) * sizeof(SQLCHAR); + 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: + col.ctype_ = SQL_C_TIMESTAMP; + col.clen_ = sizeof(timestamp); + break; + case SQL_CHAR: + case SQL_VARCHAR: + col.ctype_ = sql_ctype::value; + col.clen_ = (col.sqlsize_ + 1) * sizeof(SQLCHAR); + if (is_blob) + { + col.clen_ = 0; + col.blob_ = true; + } + break; + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_XML: + col.ctype_ = sql_ctype::value; + col.clen_ = (col.sqlsize_ + 1) * sizeof(SQLWCHAR); + if (is_blob) + { + col.clen_ = 0; + col.blob_ = true; + } + break; + case SQL_LONGVARCHAR: + col.ctype_ = sql_ctype::value; + col.blob_ = true; + col.clen_ = 0; + break; + case SQL_WLONGVARCHAR: + col.ctype_ = sql_ctype::value; + 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[static_cast(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); + col.bound_ = true; + } + } + } + +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 = *ensure_pdata(column); + return; + case SQL_C_TIMESTAMP: + { + timestamp stamp = *ensure_pdata(column); + date d = {stamp.year, stamp.month, stamp.day}; + result = d; + return; + } + } + throw type_incompatible_error(); +} + +template <> +inline void result::result_impl::get_ref_impl