//! \file nanodbc.cpp Implementation details. #ifndef DOXYGEN #include "nanodbc.h" #include #include #include #include #include #include #include #include #include #include #include #if defined(_MSC_VER) && _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. #pragma warning(disable:4996) // warning about snprintf() deprecated. #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 #define NOMINMAX #include #endif #include #include /////////////////////////////////////////////////////////////////////////////////////////////////// // Unicode Support /////////////////////////////////////////////////////////////////////////////////////////////////// #if defined(_MSC_VER) #ifdef NANODBC_USE_UNICODE #define NANODBC_TEXT(s) L ## s #define NANODBC_SNPRINTF std::swprintf #define NANODBC_STRFTIME std::wcsftime #define NANODBC_STRLEN std::wcslen #define NANADBC_STRNCMP std::wcsncmp #define NANODBC_UNICODE(f) f ## W #define NANODBC_SQLCHAR SQLWCHAR #else #define NANODBC_TEXT(s) s #define NANODBC_SNPRINTF _snprintf #define NANODBC_STRFTIME std::strftime #define NANODBC_STRLEN std::strlen #define NANADBC_STRNCMP std::strncmp #define NANODBC_UNICODE(f) f #define NANODBC_SQLCHAR SQLCHAR #endif #else #ifdef NANODBC_USE_UNICODE #define NANODBC_TEXT(s) L ## s #define NANODBC_SNPRINTF std::swprintf #define NANODBC_STRFTIME std::wcsftime #define NANODBC_STRLEN std::wcslen #define NANADBC_STRNCMP std::wcsncmp #define NANODBC_UNICODE(f) f ## W #define NANODBC_SQLCHAR SQLWCHAR #else #define NANODBC_TEXT(s) s #define NANODBC_SNPRINTF std::snprintf #define NANODBC_STRFTIME std::strftime #define NANODBC_STRLEN std::strlen #define NANADBC_STRNCMP std::strncmp #define NANODBC_UNICODE(f) f #define NANODBC_SQLCHAR SQLCHAR #endif #endif /////////////////////////////////////////////////////////////////////////////////////////////////// // ODBC API Interface /////////////////////////////////////////////////////////////////////////////////////////////////// #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 /////////////////////////////////////////////////////////////////////////////////////////////////// // Error and Exception Handling /////////////////////////////////////////////////////////////////////////////////////////////////// namespace { #ifdef NANODBC_ODBC_API_DEBUG inline nanodbc::string_type return_code(RETCODE rc) { switch(rc) { case SQL_SUCCESS: return NANODBC_UNICODE("SQL_SUCCESS"); case SQL_SUCCESS_WITH_INFO: return NANODBC_UNICODE("SQL_SUCCESS_WITH_INFO"); case SQL_ERROR: return NANODBC_UNICODE("SQL_ERROR"); case SQL_INVALID_HANDLE: return NANODBC_UNICODE("SQL_INVALID_HANDLE"); case SQL_NO_DATA: return NANODBC_UNICODE("SQL_NO_DATA"); case SQL_NEED_DATA: return NANODBC_UNICODE("SQL_NEED_DATA"); case SQL_STILL_EXECUTING: return NANODBC_UNICODE("SQL_STILL_EXECUTING"); } 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) << " | "; #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 std::wstring& in, std::string& out) { std::wstring_convert> conv; out = conv.to_bytes(in); } #ifdef NANODBC_USE_UNICODE inline void convert(const std::string& in, std::wstring& out) { std::wstring_convert> conv; out = conv.from_bytes(in); } inline void convert(const std::wstring& in, std::wstring & 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) { 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 = 0; SQLSMALLINT total_bytes; NANODBC_SQLCHAR sql_state[6]; RETCODE rc; do { NANODBC_CALL_RC( NANODBC_UNICODE(SQLGetDiagRec) , rc , handle_type , handle , i , sql_state , &native_error , 0 , 0 , &total_bytes); if(success(rc) && total_bytes > 0) sql_message.resize(total_bytes + 1); NANODBC_CALL_RC( NANODBC_UNICODE(SQLGetDiagRec) , rc , handle_type , handle , 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++; } while(rc != SQL_NO_DATA); convert(result, rvalue); std::string status(&sql_state[0], &sql_state[arrlen(sql_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 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(void* handle, short handle_type, const std::string& info) : std::runtime_error(info + recent_error(handle, handle_type)) { } const char* database_error::what() const noexcept { return std::runtime_error::what(); } } // 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__) ": ") \ /**/ /////////////////////////////////////////////////////////////////////////////////////////////////// // Implementation Details /////////////////////////////////////////////////////////////////////////////////////////////////// namespace { using namespace std; // if int64_t is in std namespace (in c++11) // 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 { #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_SSHORT; }; template<> struct sql_ctype { static const SQLSMALLINT value = SQL_C_USHORT; }; template<> struct sql_ctype { static const SQLSMALLINT value = SQL_C_SLONG; }; template<> struct sql_ctype { static const SQLSMALLINT value = SQL_C_ULONG; }; template<> struct sql_ctype { static const SQLSMALLINT value = SQL_C_SBIGINT; }; template<> struct sql_ctype { 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 { static const SQLSMALLINT value = SQL_C_DATE; }; 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) , rowset_size_(0) , 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_; long rowset_size_; nanodbc::null_type* cbdata_; char* pdata_; }; // Allocates the native ODBC handles. inline void allocate_handle(SQLHENV& env, SQLHDBC& conn) { 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)SQL_OV_ODBC3 , SQL_IS_UINTEGER); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV); 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 /////////////////////////////////////////////////////////////////////////////////////////////////// // Private Implementation: connection /////////////////////////////////////////////////////////////////////////////////////////////////// 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() noexcept { try { disconnect(); } catch(...) { // ignore exceptions thrown during disconnect } NANODBC_CALL( SQLFreeHandle , SQL_HANDLE_DBC , conn_); NANODBC_CALL( SQLFreeHandle , SQL_HANDLE_ENV , env_); } void connect( const string_type& dsn , const string_type& user , const string_type& pass , long timeout) { 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)timeout , 0); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); NANODBC_CALL_RC( NANODBC_UNICODE(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)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); connected_ = success(rc); } void connect(const string_type& connection_string, long timeout) { 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)timeout , 0); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); NANODBC_SQLCHAR dsn[1024]; SQLSMALLINT dsn_size = 0; NANODBC_CALL_RC( NANODBC_UNICODE(SQLDriverConnect) , rc , conn_ , 0 , (NANODBC_SQLCHAR*)connection_string.c_str(), SQL_NTS , dsn , sizeof(dsn) / sizeof(NANODBC_SQLCHAR) , &dsn_size , SQL_DRIVER_NOPROMPT); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC); connected_ = success(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_; } string_type driver_name() const { NANODBC_SQLCHAR name[1024]; SQLSMALLINT length; RETCODE rc; NANODBC_CALL_RC( NANODBC_UNICODE(SQLGetInfo) , rc , conn_ , SQL_DRIVER_NAME , 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() { return ++transactions_; } bool rollback() const { return rollback_; } void rollback(bool onoff) { rollback_ = onoff; } private: HENV env_; HDBC conn_; bool connected_; std::size_t transactions_; bool rollback_; // if true, this connection is marked for eventual transaction rollback }; } // namespace nanodbc /////////////////////////////////////////////////////////////////////////////////////////////////// // Private Implementation: transaction /////////////////////////////////////////////////////////////////////////////////////////////////// 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 /////////////////////////////////////////////////////////////////////////////////////////////////// // Private Implementation: statement /////////////////////////////////////////////////////////////////////////////////////////////////// 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_() { } statement_impl(class connection& conn) : stmt_(0) , open_(false) , conn_() , bind_len_or_null_() { open(conn); } statement_impl(class connection& conn, const string_type& query, long timeout) : stmt_(0) , open_(false) , conn_() , bind_len_or_null_() { prepare(conn, query, timeout); } ~statement_impl() 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); } void prepare(const string_type& query, long timeout) { if(!open()) throw programming_error("statement has no associated open connection"); RETCODE rc; NANODBC_CALL_RC( NANODBC_UNICODE(SQLPrepare) , rc , stmt_ , (NANODBC_SQLCHAR*)query.c_str() , (SQLINTEGER)query.size()); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); this->timeout(timeout); } void timeout(long timeout) { RETCODE rc; NANODBC_CALL_RC( SQLSetStmtAttr , rc , stmt_ , SQL_ATTR_QUERY_TIMEOUT , (SQLPOINTER)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); } 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) { open(conn); RETCODE rc; NANODBC_CALL_RC( SQLSetStmtAttr , rc , stmt_ , SQL_ATTR_PARAMSET_SIZE , (SQLPOINTER)batch_operations , 0); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); this->timeout(timeout); NANODBC_CALL_RC( NANODBC_UNICODE(SQLExecDirect) , rc , stmt_ , (NANODBC_SQLCHAR*)query.c_str() , SQL_NTS); if(!success(rc) && rc != SQL_NO_DATA) 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) { 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); } NANODBC_CALL_RC( SQLSetStmtAttr , rc , stmt_ , SQL_ATTR_PARAMSET_SIZE , (SQLPOINTER)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) 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"); RETCODE rc; NANODBC_CALL_RC( NANODBC_UNICODE(SQLProcedureColumns) , rc , stmt_ , (NANODBC_SQLCHAR*)(catalog.empty() ? NULL : catalog.c_str()) , (catalog.empty() ? 0 : SQL_NTS) , (NANODBC_SQLCHAR*)(schema.empty() ? NULL : schema.c_str()) , (schema.empty() ? 0 : SQL_NTS) , (NANODBC_SQLCHAR*)procedure.c_str() , SQL_NTS , (NANODBC_SQLCHAR*)(column.empty() ? NULL : 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); return rows; } short columns() const { SQLSMALLINT cols; RETCODE rc; NANODBC_CALL_RC( SQLNumResultCols , rc , stmt_ , &cols); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); return cols; } void reset_parameters() noexcept { NANODBC_CALL( SQLFreeStmt , stmt_ , SQL_RESET_PARAMS); } unsigned long parameter_size(short param) const { RETCODE rc; SQLSMALLINT data_type; SQLULEN parameter_size; NANODBC_CALL_RC( SQLDescribeParam , rc , stmt_ , param + 1 , &data_type , ¶meter_size , 0 , 0); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); return parameter_size; } static SQLSMALLINT param_type_from_direction(param_direction direction) { switch(direction) { default: assert(false); // fallthrough 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; } // Remove warning C4702 : unreachable code nanodbc.cpp 1284 Nanodbc assert(false); } // initializes bind_len_or_null_ and gets information for bind void prepare_bind( short param , std::size_t elements , param_direction direction , SQLSMALLINT& data_type , SQLSMALLINT& param_type , SQLULEN& parameter_size) { RETCODE rc; NANODBC_CALL_RC( SQLDescribeParam , rc , stmt_ , param + 1 , &data_type , ¶meter_size , 0 , 0); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); param_type = param_type_from_direction(direction); if(!bind_len_or_null_.count(param)) bind_len_or_null_[param] = std::vector(); std::vector().swap(bind_len_or_null_[param]); // ODBC weirdness: this must be at least 8 elements in size const std::size_t indicator_size = elements > 8 ? elements : 8; bind_len_or_null_[param].reserve(indicator_size); bind_len_or_null_[param].assign(indicator_size, SQL_NULL_DATA); } // calls actual ODBC bind parameter function template void bind_parameter( short param , const T* data , std::size_t /*elements*/ , SQLSMALLINT data_type , SQLSMALLINT param_type , SQLULEN parameter_size) { RETCODE rc; NANODBC_CALL_RC( SQLBindParameter , rc , stmt_ // handle , param + 1 // parameter number , param_type // input or output type , sql_ctype::value // value type , data_type // parameter type , parameter_size // column size ignored for many types, but needed for strings , 0 // decimal digits , (SQLPOINTER)data // parameter value , parameter_size // buffer length , bind_len_or_null_[param].data()); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } // handles a single value (possibly a single string value), or multiple non-string values template void bind(short param, const T* values, std::size_t elements, param_direction direction); // handles multiple string values void bind_strings( short param , const string_type::value_type* values , std::size_t /*length*/ , std::size_t elements , param_direction direction) { bind(param, values, elements, direction); } // handles multiple null values void bind_null(short param, std::size_t elements) { SQLSMALLINT data_type; SQLSMALLINT param_type; SQLULEN parameter_size; prepare_bind(param, elements, PARAM_IN, data_type, param_type, parameter_size); RETCODE rc; NANODBC_CALL_RC( SQLBindParameter , rc , stmt_ , param + 1 , param_type , SQL_C_CHAR , data_type , parameter_size // column size ignored for many types, but needed for strings , 0 , (SQLPOINTER)0 // null value , 0 // parameter_size , bind_len_or_null_[param].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; } // handles multiple non-string values with a null sentry template void bind( short param , const T* values , std::size_t elements , const bool* nulls , const T* null_sentry , param_direction direction); // handles multiple string values void bind_strings( short param , const string_type::value_type* values , std::size_t length , std::size_t elements , const bool* nulls , const string_type::value_type* null_sentry , param_direction direction); private: HSTMT stmt_; bool open_; class connection conn_; std::map > bind_len_or_null_; }; // Supports code like: query.bind(0, std_string.c_str()) // In this case, we need to pass NULL to the final parameter of SQLBindParameter(). template<> void statement::statement_impl::bind_parameter( short param , const string_type::value_type* data , std::size_t elements , SQLSMALLINT data_type , SQLSMALLINT param_type , SQLULEN parameter_size) { RETCODE rc; NANODBC_CALL_RC( SQLBindParameter , rc , stmt_ // handle , param + 1 // parameter number , param_type // input or output type , sql_ctype::value // value type , data_type // parameter type , parameter_size // column size ignored for many types, but needed for strings , 0 // decimal digits , (SQLPOINTER)data // parameter value , parameter_size // buffer length , (elements <= 1 ? NULL : bind_len_or_null_[param].data())); if(!success(rc)) NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT); } template void statement::statement_impl::bind( short param , const T* values , std::size_t elements , param_direction direction) { SQLSMALLINT data_type; SQLSMALLINT param_type; SQLULEN parameter_size; prepare_bind(param, elements, direction, data_type, param_type, parameter_size); for(std::size_t i = 0; i < elements; ++i) bind_len_or_null_[param][i] = parameter_size; bind_parameter(param, values, elements, data_type, param_type, parameter_size); } template void statement::statement_impl::bind( short param , const T* values , std::size_t elements , const bool* nulls , const T* null_sentry , param_direction direction) { SQLSMALLINT data_type; SQLSMALLINT param_type; SQLULEN parameter_size; prepare_bind(param, elements, direction, data_type, param_type, parameter_size); for(std::size_t i = 0; i < elements; ++i) if((null_sentry && !equals(values[i], *null_sentry)) || (nulls && !nulls[i])) bind_len_or_null_[param][i] = parameter_size; bind_parameter(param, values, elements, data_type, param_type, parameter_size); } void statement::statement_impl::bind_strings( short param , const string_type::value_type* values , std::size_t length , std::size_t elements , const bool* nulls , const string_type::value_type* null_sentry , param_direction direction) { SQLSMALLINT data_type; SQLSMALLINT param_type; SQLULEN parameter_size; prepare_bind(param, elements, direction, data_type, param_type, parameter_size); if(null_sentry) { const string_type rhs(null_sentry); for(std::size_t i = 0; i < elements; ++i) { const string_type lhs(values + i * length, values + (i + 1) * length); if(NANADBC_STRNCMP(lhs.c_str(), rhs.c_str(), length)) bind_len_or_null_[param][i] = parameter_size; } } else if(nulls) { for(std::size_t i = 0; i < elements; ++i) { if(!nulls[i]) bind_len_or_null_[param][i] = SQL_NTS; // null terminated } } bind_parameter(param, values, elements, data_type, param_type, parameter_size); } 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 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 /////////////////////////////////////////////////////////////////////////////////////////////////// // Private Implementation: result /////////////////////////////////////////////////////////////////////////////////////////////////// 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_() { RETCODE rc; NANODBC_CALL_RC( SQLSetStmtAttr , rc , stmt_.native_statement_handle() , SQL_ATTR_ROW_ARRAY_SIZE , (SQLPOINTER)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(); } long rows() const noexcept { return 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() { if(rows() && ++rowset_position_ < rowset_size_) return rowset_position_ < rows(); rowset_position_ = 0; return fetch(0, SQL_FETCH_NEXT); } 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); return pos - 1 + rowset_position_; } bool end() const noexcept { 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); } 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]; return col.sqlsize_; } 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_; } 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; 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() noexcept { for(short i = 0; i < bound_columns_size_; ++i) { bound_column& col = bound_columns_[i]; for(long j = 0; j < col.rowset_size_; ++j) col.cbdata_[j] = 0; if(col.blob_ && col.pdata_) release_bound_resources(i); } } void release_bound_resources(short column) noexcept { 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_ = NULL; bound_columns_size_ = 0; bound_columns_by_name_.clear(); } bool fetch(long rows, SQLUSMALLINT orientation) { before_move(); RETCODE rc; NANODBC_CALL_RC( SQLFetchScroll , rc , stmt_.native_statement_handle() , orientation , rows); if(rc == SQL_NO_DATA) return false; 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; assert(!bound_columns_); 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; for(SQLSMALLINT i = 0; i < n_columns; ++i) { NANODBC_CALL_RC( NANODBC_UNICODE(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: col.ctype_ = SQL_C_LONG; col.clen_ = sizeof(int32_t); break; 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_TIMESTAMP: case SQL_TYPE_TIMESTAMP: col.ctype_ = SQL_C_TIMESTAMP; col.clen_ = sizeof(timestamp); break; case SQL_CHAR: case SQL_VARCHAR: 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: col.ctype_ = SQL_C_BINARY; col.clen_ = col.sqlsize_ + sizeof(NANODBC_SQLCHAR); break; case SQL_LONGVARBINARY: 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.rowset_size_ = rowset_size_; 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_; }; 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 = *((date*)(col.pdata_ + rowset_position_ * col.clen_)); return; case SQL_C_TIMESTAMP: { timestamp stamp = *( (timestamp*)( 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(short column, timestamp& result) const { bound_column& col = bound_columns_[column]; switch(col.ctype_) { case SQL_C_DATE: { date d = *((date*)(col.pdata_ + rowset_position_ * col.clen_)); timestamp stamp = { d.year, d.month, d.day, 0, 0, 0, 0 }; result = stamp; return; } case SQL_C_TIMESTAMP: result = *((timestamp*)(col.pdata_ + rowset_position_ * col.clen_)); return; } throw type_incompatible_error(); } template<> inline void result::result_impl::get_ref_impl(short column, string_type& result) const { const bound_column& col = bound_columns_[column]; const SQLULEN column_size = col.sqlsize_; switch(col.ctype_) { case SQL_C_CHAR: { if(col.blob_) { // Input is always std::string, while output may be std::string or std::wstring std::stringstream ss; char buff[1024] = {0}; std::size_t buff_size = sizeof(buff); SQLLEN ValueLenOrInd; SQLRETURN rc; void* handle = native_statement_handle(); do { NANODBC_CALL_RC( SQLGetData , rc , handle // StatementHandle , column + 1 // Col_or_Param_Num , SQL_C_CHAR // TargetType , buff // TargetValuePtr , buff_size // BufferLength , &ValueLenOrInd); // StrLen_or_IndPtr if (ValueLenOrInd > 0) ss << buff; } while(rc > 0); convert(ss.str(), result); } else { const char* s = col.pdata_ + rowset_position_ * col.clen_; const std::string::size_type str_size = std::strlen(s); result.assign(s, s + str_size); } return; } case SQL_C_WCHAR: { if (col.blob_) { // Input is always std::wstring, output might be std::string or std::wstring. // Use a string builder to build the output string. std::wstringstream ss; wchar_t buffer[512] = {0}; std::size_t buffer_size = sizeof(buffer); SQLLEN ValueLenOrInd; SQLRETURN rc; void* handle = native_statement_handle(); do { NANODBC_CALL_RC( SQLGetData , rc , handle // StatementHandle , column + 1 // Col_or_Param_Num , SQL_C_WCHAR // TargetType , buffer // TargetValuePtr , buffer_size // BufferLength , &ValueLenOrInd); // StrLen_or_IndPtr if (ValueLenOrInd > 0) ss << buffer; } while (rc > 0); convert(ss.str(), result); } else { // Type is unicode in the database, convert if necessary const SQLWCHAR* s = reinterpret_cast(col.pdata_ + rowset_position_ * col.clen_); const string_type::size_type str_size = *col.cbdata_ / sizeof(SQLWCHAR); std::wstring temp(s, s + str_size); convert(temp, result); } return; } case SQL_C_GUID: case SQL_C_BINARY: { if(col.blob_) throw std::runtime_error("blob not implemented yet"); const char* s = col.pdata_ + rowset_position_ * col.clen_; result.assign(s, s + column_size); return; } case SQL_C_LONG: { result.resize(column_size); if(NANODBC_SNPRINTF( const_cast(result.data()) , column_size , NANODBC_TEXT("%d") , *(int32_t*)(col.pdata_ + rowset_position_ * col.clen_)) == -1) throw type_incompatible_error(); result.resize(NANODBC_STRLEN(result.c_str())); return; } case SQL_C_SBIGINT: { using namespace std; // in case intmax_t is in namespace std result.resize(column_size); if(NANODBC_SNPRINTF( const_cast(result.data()) , column_size , NANODBC_TEXT("%jd") , (intmax_t) *(int64_t*)(col.pdata_ + rowset_position_ * col.clen_)) == -1) throw type_incompatible_error(); result.resize(NANODBC_STRLEN(result.c_str())); return; } case SQL_C_FLOAT: { result.resize(column_size); if(NANODBC_SNPRINTF( const_cast(result.data()) , column_size , NANODBC_TEXT("%f") , *(float*)(col.pdata_ + rowset_position_ * col.clen_)) == -1) throw type_incompatible_error(); result.resize(NANODBC_STRLEN(result.c_str())); return; } case SQL_C_DOUBLE: { result.resize(column_size); if(NANODBC_SNPRINTF( const_cast(result.data()) , column_size , NANODBC_TEXT("%lf") , *(double*)(col.pdata_ + rowset_position_ * col.clen_)) == -1) throw type_incompatible_error(); result.resize(NANODBC_STRLEN(result.c_str())); return; } case SQL_C_DATE: { date d = *((date*)(col.pdata_ + rowset_position_ * col.clen_)); std::tm st = { 0 }; st.tm_year = d.year - 1900; st.tm_mon = d.month - 1; st.tm_mday = d.day; char* old_lc_time = std::setlocale(LC_TIME, NULL); std::setlocale(LC_TIME, ""); string_type::value_type date_str[512]; NANODBC_STRFTIME( date_str , sizeof(date_str) / sizeof(string_type::value_type) , NANODBC_TEXT("%Y-%m-%d") , &st); std::setlocale(LC_TIME, old_lc_time); result.assign(date_str); return; } case SQL_C_TIMESTAMP: { timestamp stamp = *((timestamp*)(col.pdata_ + rowset_position_ * col.clen_)); std::tm st = { 0 }; st.tm_year = stamp.year - 1900; st.tm_mon = stamp.month - 1; st.tm_mday = stamp.day; st.tm_hour = stamp.hour; st.tm_min = stamp.min; st.tm_sec = stamp.sec; char* old_lc_time = std::setlocale(LC_TIME, NULL); std::setlocale(LC_TIME, ""); string_type::value_type date_str[512]; NANODBC_STRFTIME( date_str , sizeof(date_str) / sizeof(string_type::value_type) , NANODBC_TEXT("%Y-%m-%d %H:%M:%S %z") , &st); std::setlocale(LC_TIME, old_lc_time); result.assign(date_str); return; } } throw type_incompatible_error(); } template void result::result_impl::get_ref_impl(short column, T& result) const { bound_column& col = bound_columns_[column]; using namespace std; // if int64_t is in std namespace (in c++11) const char* s = col.pdata_ + rowset_position_ * col.clen_; switch(col.ctype_) { case SQL_C_CHAR: result = (T)*(char*)(s); return; case SQL_C_SSHORT: result = (T)*(short*)(s); return; case SQL_C_USHORT: result = (T)*(unsigned short*)(s); return; case SQL_C_LONG: result = (T)*(int32_t*)(s); return; case SQL_C_SLONG: result = (T)*(int32_t*)(s); return; case SQL_C_ULONG: result = (T)*(uint32_t*)(s); return; case SQL_C_FLOAT: result = (T)*(float*)(s); return; case SQL_C_DOUBLE: result = (T)*(double*)(s); return; case SQL_C_SBIGINT: result = (T)*(int64_t*)(s); return; case SQL_C_UBIGINT: result = (T)*(uint64_t*)(s); return; } throw type_incompatible_error(); } } // namespace nanodbc /////////////////////////////////////////////////////////////////////////////////////////////////// // Free Functions /////////////////////////////////////////////////////////////////////////////////////////////////// namespace nanodbc { result execute(connection& conn, const string_type& query, long batch_operations, long timeout) { class statement statement; return statement.execute_direct(conn, query, batch_operations, timeout); } void just_execute(connection& conn, const string_type& query, long batch_operations, long timeout) { class statement statement; statement.just_execute_direct(conn, query, batch_operations, timeout); } result execute(statement& stmt, long batch_operations) { return stmt.execute(batch_operations); } void just_execute(statement& stmt, long batch_operations) { return stmt.just_execute(batch_operations); } result transact(statement& stmt, long batch_operations) { class transaction transaction(stmt.connection()); result rvalue = stmt.execute(batch_operations); transaction.commit(); return rvalue; } void just_transact(statement& stmt, long batch_operations) { class transaction transaction(stmt.connection()); stmt.just_execute(batch_operations); transaction.commit(); } void prepare(statement& stmt, const string_type& query, long timeout) { stmt.prepare(stmt.connection(), query, timeout); } } // namespace nanodbc /////////////////////////////////////////////////////////////////////////////////////////////////// // Pimpl Forwards: connection /////////////////////////////////////////////////////////////////////////////////////////////////// namespace nanodbc { connection::connection() : impl_(new connection_impl()) { } connection::connection(const connection& rhs) : impl_(rhs.impl_) { } connection& connection::operator=(connection rhs) { swap(rhs); return *this; } void connection::swap(connection& rhs) noexcept { using std::swap; swap(impl_, rhs.impl_); } connection::connection( const string_type& dsn , const string_type& user , const string_type& pass , long timeout) : impl_(new connection_impl(dsn, user, pass, timeout)) { } connection::connection(const string_type& connection_string, long timeout) : impl_(new connection_impl(connection_string, timeout)) { } connection::~connection() noexcept { } void connection::connect( const string_type& dsn , const string_type& user , const string_type& pass , long timeout) { impl_->connect(dsn, user, pass, timeout); } void connection::connect(const string_type& connection_string, long timeout) { impl_->connect(connection_string, timeout); } bool connection::connected() const { return impl_->connected(); } void connection::disconnect() { impl_->disconnect(); } std::size_t connection::transactions() const { return impl_->transactions(); } void* connection::native_dbc_handle() const { return impl_->native_dbc_handle(); } void* connection::native_env_handle() const { return impl_->native_env_handle(); } string_type connection::driver_name() const { return impl_->driver_name(); } std::size_t connection::ref_transaction() { return impl_->ref_transaction(); } std::size_t connection::unref_transaction() { return impl_->unref_transaction(); } bool connection::rollback() const { return impl_->rollback(); } void connection::rollback(bool onoff) { impl_->rollback(onoff); } } // namespace nanodbc /////////////////////////////////////////////////////////////////////////////////////////////////// // Pimpl Forwards: transaction /////////////////////////////////////////////////////////////////////////////////////////////////// namespace nanodbc { transaction::transaction(const class connection& conn) : impl_(new transaction_impl(conn)) { } transaction::transaction(const transaction& rhs) : impl_(rhs.impl_) { } transaction& transaction::operator=(transaction rhs) { swap(rhs); return *this; } void transaction::swap(transaction& rhs) noexcept { using std::swap; swap(impl_, rhs.impl_); } transaction::~transaction() noexcept { } void transaction::commit() { impl_->commit(); } void transaction::rollback() noexcept { impl_->rollback(); } class connection& transaction::connection() { return impl_->connection(); } const class connection& transaction::connection() const { return impl_->connection(); } transaction::operator class connection&() { return impl_->connection(); } transaction::operator const class connection&() const { return impl_->connection(); } } // namespace nanodbc /////////////////////////////////////////////////////////////////////////////////////////////////// // Pimpl Forwards: statement /////////////////////////////////////////////////////////////////////////////////////////////////// namespace nanodbc { statement::statement() : impl_(new statement_impl()) { } statement::statement(class connection& conn) : impl_(new statement_impl(conn)) { } statement::statement(class connection& conn, const string_type& query, long timeout) : impl_(new statement_impl(conn, query, timeout)) { } statement::statement(const statement& rhs) : impl_(rhs.impl_) { } statement& statement::operator=(statement rhs) { swap(rhs); return *this; } void statement::swap(statement& rhs) noexcept { using std::swap; swap(impl_, rhs.impl_); } statement::~statement() noexcept { } void statement::open(class connection& conn) { impl_->open(conn); } bool statement::open() const { return impl_->open(); } bool statement::connected() const { return impl_->connected(); } const class connection& statement::connection() const { return impl_->connection(); } class connection& statement::connection() { return impl_->connection(); } void* statement::native_statement_handle() const { return impl_->native_statement_handle(); } void statement::close() { impl_->close(); } void statement::cancel() { impl_->cancel(); } void statement::prepare(class connection& conn, const string_type& query, long timeout) { impl_->prepare(conn, query, timeout); } void statement::prepare(const string_type& query, long timeout) { impl_->prepare(query, timeout); } void statement::timeout(long timeout) { impl_->timeout(timeout); } result statement::execute_direct( class connection& conn , const string_type& query , long batch_operations , long timeout) { return impl_->execute_direct(conn, query, batch_operations, timeout, *this); } void statement::just_execute_direct( class connection& conn , const string_type& query , long batch_operations , long timeout) { impl_->just_execute_direct(conn, query, batch_operations, timeout, *this); } result statement::execute(long batch_operations, long timeout) { return impl_->execute(batch_operations, timeout, *this); } void statement::just_execute(long batch_operations, long timeout) { impl_->just_execute(batch_operations, timeout, *this); } result statement::procedure_columns( const string_type& catalog , const string_type& schema , const string_type& procedure , const string_type& column) { return impl_->procedure_columns(catalog, schema, procedure, column, *this); } long statement::affected_rows() const { return impl_->affected_rows(); } short statement::columns() const { return impl_->columns(); } void statement::reset_parameters() noexcept { impl_->reset_parameters(); } unsigned long statement::parameter_size(short param) const { return impl_->parameter_size(param); } // We need to instantiate each form of bind() for each of our supported data types. #define NANODBC_INSTANTIATE_BINDS(type) \ template void statement::bind(short, const type*, param_direction); /* 1-ary */ \ template void statement::bind(short, const type*, std::size_t, param_direction); /* n-ary */ \ template void statement::bind(short, const type*, std::size_t, const type*, param_direction); /* n-ary, sentry */ \ template void statement::bind(short, const type*, std::size_t, const bool*, param_direction) /* n-ary, flags */ \ /**/ // The following are the only supported instantiations of statement::bind(). NANODBC_INSTANTIATE_BINDS(string_type::value_type); NANODBC_INSTANTIATE_BINDS(short); NANODBC_INSTANTIATE_BINDS(unsigned short); NANODBC_INSTANTIATE_BINDS(int32_t); NANODBC_INSTANTIATE_BINDS(uint32_t); NANODBC_INSTANTIATE_BINDS(int64_t); NANODBC_INSTANTIATE_BINDS(uint64_t); NANODBC_INSTANTIATE_BINDS(float); NANODBC_INSTANTIATE_BINDS(double); NANODBC_INSTANTIATE_BINDS(date); NANODBC_INSTANTIATE_BINDS(timestamp); #undef NANODBC_INSTANTIATE_BINDS template void statement::bind(short param, const T* value, param_direction direction) { impl_->bind(param, value, 1, direction); } template void statement::bind(short param, const T* values, std::size_t elements, param_direction direction) { impl_->bind(param, values, elements, direction); } template void statement::bind( short param , const T* values , std::size_t elements , const T* null_sentry , param_direction direction) { impl_->bind(param, values, elements, 0, null_sentry, direction); } template void statement::bind( short param , const T* values , std::size_t elements , const bool* nulls , param_direction direction) { impl_->bind(param, values, elements, nulls, (T*)0, direction); } void statement::bind_strings( short param , const string_type::value_type* values , std::size_t length , std::size_t elements , param_direction direction) { impl_->bind_strings(param, values, length, elements, direction); } void statement::bind_strings( short param , const string_type::value_type* values , std::size_t length , std::size_t elements , const string_type::value_type* null_sentry , param_direction direction) { impl_->bind_strings(param, values, length, elements, (bool*)0, null_sentry, direction); } void statement::bind_strings( short param , const string_type::value_type* values , std::size_t length , std::size_t elements , const bool* nulls , param_direction direction) { impl_->bind_strings( param , values , length , elements , nulls , (string_type::value_type*)0 , direction); } void statement::bind_null(short param, std::size_t elements) { impl_->bind_null(param, elements); } } // namespace nanodbc /////////////////////////////////////////////////////////////////////////////////////////////////// // Pimpl Forwards: result /////////////////////////////////////////////////////////////////////////////////////////////////// namespace nanodbc { result::result() : impl_() { } result::~result() noexcept { } result::result(statement stmt, long rowset_size) : impl_(new result_impl(stmt, rowset_size)) { } result::result(const result& rhs) : impl_(rhs.impl_) { } result& result::operator=(result rhs) { swap(rhs); return *this; } void result::swap(result& rhs) noexcept { using std::swap; swap(impl_, rhs.impl_); } void* result::native_statement_handle() const { return impl_->native_statement_handle(); } long result::rowset_size() const noexcept { return impl_->rowset_size(); } long result::affected_rows() const { return impl_->affected_rows(); } long result::rows() const noexcept { return impl_->rows(); } short result::columns() const { return impl_->columns(); } bool result::first() { return impl_->first(); } bool result::last() { return impl_->last(); } bool result::next() { return impl_->next(); } bool result::prior() { return impl_->prior(); } bool result::move(long row) { return impl_->move(row); } bool result::skip(long rows) { return impl_->skip(rows); } unsigned long result::position() const { return impl_->position(); } bool result::end() const noexcept { return impl_->end(); } bool result::is_null(short column) const { return impl_->is_null(column); } bool result::is_null(const string_type& column_name) const { return impl_->is_null(column_name); } string_type result::column_name(short column) const { return impl_->column_name(column); } long result::column_size(short column) const { return impl_->column_size(column); } short result::column(const string_type& column_name) const { return impl_->column(column_name); } int result::column_datatype(short column) const { return impl_->column_datatype(column); } int result::column_datatype(const string_type& column_name) const { return impl_->column_datatype(column_name); } int result::column_c_datatype(short column) const { return impl_->column_c_datatype(column); } int result::column_c_datatype(const string_type& column_name) const { return impl_->column_c_datatype(column_name); } bool result::next_result() { return impl_->next_result(); } template void result::get_ref(short column, T& result) const { return impl_->get_ref(column, result); } template void result::get_ref(short column, const T& fallback, T& result) const { return impl_->get_ref(column, fallback, result); } template void result::get_ref(const string_type& column_name, T& result) const { return impl_->get_ref(column_name, result); } template void result::get_ref(const string_type& column_name, const T& fallback, T& result) const { return impl_->get_ref(column_name, fallback, result); } template T result::get(short column) const { return impl_->get(column); } template T result::get(short column, const T& fallback) const { return impl_->get(column, fallback); } template T result::get(const string_type& column_name) const { return impl_->get(column_name); } template T result::get(const string_type& column_name, const T& fallback) const { return impl_->get(column_name, fallback); } result::operator bool() const { return static_cast(impl_); } // The following are the only supported instantiations of result::get_ref(). template void result::get_ref(short, string_type::value_type&) const; template void result::get_ref(short, short&) const; template void result::get_ref(short, unsigned short&) const; template void result::get_ref(short, int32_t&) const; template void result::get_ref(short, uint32_t&) const; template void result::get_ref(short, int64_t&) const; template void result::get_ref(short, uint64_t&) const; template void result::get_ref(short, float&) const; template void result::get_ref(short, double&) const; template void result::get_ref(short, string_type&) const; template void result::get_ref(short, date&) const; template void result::get_ref(short, timestamp&) const; template void result::get_ref(const string_type&, string_type::value_type&) const; template void result::get_ref(const string_type&, short&) const; template void result::get_ref(const string_type&, unsigned short&) const; template void result::get_ref(const string_type&, int32_t&) const; template void result::get_ref(const string_type&, uint32_t&) const; template void result::get_ref(const string_type&, int64_t&) const; template void result::get_ref(const string_type&, uint64_t&) const; template void result::get_ref(const string_type&, float&) const; template void result::get_ref(const string_type&, double&) const; template void result::get_ref(const string_type&, string_type&) const; template void result::get_ref(const string_type&, date&) const; template void result::get_ref(const string_type&, timestamp&) const; // The following are the only supported instantiations of result::get_ref() with fallback. template void result::get_ref(short, const string_type::value_type&, string_type::value_type&) const; template void result::get_ref(short, const short&, short&) const; template void result::get_ref(short, const unsigned short&, unsigned short&) const; template void result::get_ref(short, const int32_t&, int32_t&) const; template void result::get_ref(short, const uint32_t&, uint32_t&) const; template void result::get_ref(short, const int64_t&, int64_t&) const; template void result::get_ref(short, const uint64_t&, uint64_t&) const; template void result::get_ref(short, const float&, float&) const; template void result::get_ref(short, const double&, double&) const; template void result::get_ref(short, const string_type&, string_type&) const; template void result::get_ref(short, const date&, date&) const; template void result::get_ref(short, const timestamp&, timestamp&) const; template void result::get_ref(const string_type&, const string_type::value_type&, string_type::value_type&) const; template void result::get_ref(const string_type&, const short&, short&) const; template void result::get_ref(const string_type&, const unsigned short&, unsigned short&) const; template void result::get_ref(const string_type&, const int32_t&, int32_t&) const; template void result::get_ref(const string_type&, const uint32_t&, uint32_t&) const; template void result::get_ref(const string_type&, const int64_t&, int64_t&) const; template void result::get_ref(const string_type&, const uint64_t&, uint64_t&) const; template void result::get_ref(const string_type&, const float&, float&) const; template void result::get_ref(const string_type&, const double&, double&) const; template void result::get_ref(const string_type&, const string_type&, string_type&) const; template void result::get_ref(const string_type&, const date&, date&) const; template void result::get_ref(const string_type&, const timestamp&, timestamp&) const; // The following are the only supported instantiations of result::get(). template string_type::value_type result::get(short) const; template short result::get(short) const; template unsigned short result::get(short) const; template int32_t result::get(short) const; template uint32_t result::get(short) const; template int64_t result::get(short) const; template uint64_t result::get(short) const; template float result::get(short) const; template double result::get(short) const; template string_type result::get(short) const; template date result::get(short) const; template timestamp result::get(short) const; template string_type::value_type result::get(const string_type&) const; template short result::get(const string_type&) const; template unsigned short result::get(const string_type&) const; template int32_t result::get(const string_type&) const; template uint32_t result::get(const string_type&) const; template int64_t result::get(const string_type&) const; template uint64_t result::get(const string_type&) const; template float result::get(const string_type&) const; template double result::get(const string_type&) const; template string_type result::get(const string_type&) const; template date result::get(const string_type&) const; template timestamp result::get(const string_type&) const; // The following are the only supported instantiations of result::get() with fallback. template string_type::value_type result::get(short, const string_type::value_type&) const; template short result::get(short, const short&) const; template unsigned short result::get(short, const unsigned short&) const; template int32_t result::get(short, const int32_t&) const; template uint32_t result::get(short, const uint32_t&) const; template int64_t result::get(short, const int64_t&) const; template uint64_t result::get(short, const uint64_t&) const; template float result::get(short, const float&) const; template double result::get(short, const double&) const; template string_type result::get(short, const string_type&) const; template date result::get(short, const date&) const; template timestamp result::get(short, const timestamp&) const; template string_type::value_type result::get(const string_type&, const string_type::value_type&) const; template short result::get(const string_type&, const short&) const; template unsigned short result::get(const string_type&, const unsigned short&) const; template int32_t result::get(const string_type&, const int32_t&) const; template uint32_t result::get(const string_type&, const uint32_t&) const; template int64_t result::get(const string_type&, const int64_t&) const; template uint64_t result::get(const string_type&, const uint64_t&) const; template float result::get(const string_type&, const float&) const; template double result::get(const string_type&, const double&) const; template string_type result::get(const string_type&, const string_type&) const; template date result::get(const string_type&, const date&) const; template timestamp result::get(const string_type&, const timestamp&) const; } // namespace nanodbc #undef NANODBC_THROW_DATABASE_ERROR #undef NANODBC_STRINGIZE #undef NANODBC_STRINGIZE_I #undef NANODBC_CALL_RC #undef NANODBC_CALL #endif // DOXYGEN