#ifndef _QTL_ODCB_H_ #define _QTL_ODCB_H_ #include #include #include #include #include #include #include #include #include #include #if !defined(_WIN32) || defined(__MINGW32__) #include #endif //_WIN32 #if (ODBCVER >= 0x0380) && (_WIN32_WINNT >= 0x0602) #define QTL_ODBC_ENABLE_ASYNC_MODE 1 #endif // ODBC 3.80 && Windows #include "qtl_common.hpp" #include "qtl_async.hpp" namespace qtl { namespace odbc { template class object; class base_database; class error : public std::exception { public: error() : m_errno(SQL_SUCCESS) {} template error(const object &h, SQLINTEGER code); error(SQLINTEGER code, const char *msg) : m_errno(code), m_errmsg(msg) {} SQLINTEGER code() const { return m_errno; } operator bool() const { return m_errno != SQL_SUCCESS && m_errno != SQL_SUCCESS_WITH_INFO; } virtual const char *what() const throw() override { return m_errmsg.data(); } private: SQLINTEGER m_errno; std::string m_errmsg; }; template class object { public: enum { handler_type = Type }; object() : m_handle(SQL_NULL_HANDLE){}; object(const object &) = delete; object(object &&src) : m_handle(src.m_handle) { src.m_handle = SQL_NULL_HANDLE; } explicit object(SQLHANDLE parent) { verify_error(SQLAllocHandle(handler_type, parent, &m_handle)); } ~object() { close(); } object &operator=(const object &) = delete; object &operator=(object &&src) { if (this != &src) { close(); m_handle = src.m_handle; src.m_handle = NULL; } return *this; } SQLHANDLE handle() const { return m_handle; } void close() { if (m_handle) { verify_error(SQLFreeHandle(handler_type, m_handle)); m_handle = SQL_NULL_HANDLE; } } void verify_error(SQLINTEGER code) const { if (code < 0) throw odbc::error(*this, code); } protected: SQLHANDLE m_handle; }; class blobbuf : public qtl::blobbuf { public: blobbuf() : m_stmt(nullptr), m_field(0) { } blobbuf(const blobbuf &) = default; blobbuf &operator=(const blobbuf &) = default; virtual ~blobbuf() { overflow(); } void open(object *stmt, SQLSMALLINT field, std::ios_base::openmode mode) { if (m_stmt && m_field) { overflow(); } assert(stmt != SQL_NULL_HANDLE); m_stmt = stmt; m_field = field; m_size = INTMAX_MAX; init_buffer(mode); } private: object *m_stmt; SQLSMALLINT m_field; protected: virtual bool read_blob(char *buffer, off_type &count, pos_type position) override { SQLLEN indicator = 0; SQLRETURN ret = SQLGetData(m_stmt->handle(), m_field + 1, SQL_C_BINARY, buffer, static_cast(count), const_cast(&indicator)); if (ret != SQL_NO_DATA) { count = (indicator > count) || (indicator == SQL_NO_TOTAL) ? count : indicator; m_stmt->verify_error(ret); return true; } else return false; } virtual void write_blob(const char *buffer, size_t count) override { m_stmt->verify_error(SQLPutData(m_stmt->handle(), (SQLPOINTER)buffer, count)); } }; class environment final : public object { public: environment() : object(SQL_NULL_HANDLE) { #if ODBCVER >= 0x0380 const SQLPOINTER version = (SQLPOINTER)SQL_OV_ODBC3_80; #else const SQLPOINTER version = (SQLPOINTER)SQL_OV_ODBC3; #endif verify_error(SQLSetEnvAttr(m_handle, SQL_ATTR_ODBC_VERSION, version, SQL_IS_INTEGER)); } environment(environment &&src) : object(std::forward(src)) {} int32_t version() const { int32_t ver = 0; verify_error(SQLGetEnvAttr(m_handle, SQL_ATTR_ODBC_VERSION, &ver, sizeof(DWORD), NULL)); return ver; } }; class base_statement : public object { public: explicit base_statement(base_database &db); base_statement(base_statement &&src) : object(std::forward(src)), m_params(std::forward>(src.m_params)) { m_binded_cols = src.m_binded_cols; src.m_binded_cols = false; m_blob_buffer = src.m_blob_buffer; src.m_blob_buffer = NULL; } ~base_statement() { if (m_blob_buffer) free(m_blob_buffer); } base_statement &operator=(base_statement &&src) { if (this != &src) { object::operator=(std::forward(src)); m_params = std::forward>(src.m_params); m_binded_cols = src.m_binded_cols; src.m_binded_cols = false; m_blob_buffer = src.m_blob_buffer; src.m_blob_buffer = NULL; } return *this; } void bind_param(size_t index, const std::nullptr_t &) { m_params[index].m_indicator = SQL_NULL_DATA; verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_DEFAULT, SQL_DEFAULT, 0, 0, NULL, 0, &m_params[index].m_indicator)); } void bind_param(size_t index, const qtl::null &) { bind_param(index, nullptr); } void bind_param(size_t index, const int8_t &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_STINYINT, SQL_TINYINT, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const uint8_t &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_UTINYINT, SQL_TINYINT, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const int16_t &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_SSHORT, SQL_SMALLINT, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const uint16_t &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_USHORT, SQL_SMALLINT, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const int32_t &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const uint32_t &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const int64_t &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const uint64_t &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_UBIGINT, SQL_BIGINT, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const double &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const float &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_FLOAT, SQL_FLOAT, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const bool &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_BIT, SQL_BIT, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const DATE_STRUCT &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_DATE, SQL_DATE, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const TIME_STRUCT &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_TIME, SQL_TIME, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const TIMESTAMP_STRUCT &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_TIMESTAMP, SQL_TIMESTAMP, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const SQLGUID &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_GUID, SQL_GUID, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const SQL_NUMERIC_STRUCT &v) { verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_NUMERIC, SQL_NUMERIC, 0, 0, (SQLPOINTER)&v, 0, NULL)); } void bind_param(size_t index, const char *v, size_t n = SQL_NTS, SQLULEN size = 0) { m_params[index].m_indicator = n; if (size == 0) size = strlen(v); verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, size, 0, (SQLPOINTER)v, 0, &m_params[index].m_indicator)); } void bind_param(size_t index, const wchar_t *v, size_t n = SQL_NTS, SQLULEN size = 0) { m_params[index].m_indicator = n; if (size == 0) size = wcslen(v); verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_WCHAR, SQL_WCHAR, size, 0, (SQLPOINTER)v, 0, &m_params[index].m_indicator)); } void bind_param(size_t index, const std::string &v) { bind_param(index, v.data(), v.size(), v.size()); } void bind_param(size_t index, const std::wstring &v) { bind_param(index, v.data(), v.size(), v.size()); } void bind_param(size_t index, const const_blob_data &v) { m_params[index].m_indicator = v.size; verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_BINARY, SQL_BINARY, v.size, 0, (SQLPOINTER)v.data, 0, &m_params[index].m_indicator)); } void bind_param(size_t index, std::istream &s) { if (m_blob_buffer == NULL) m_blob_buffer = malloc(blob_buffer_size); m_params[index].m_data = m_blob_buffer; m_params[index].m_size = blob_buffer_size; m_params[index].m_indicator = SQL_LEN_DATA_AT_EXEC(m_params[index].m_size); verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, INT_MAX, 0, &m_params[index], 0, &m_params[index].m_indicator)); m_params[index].m_after_fetch = [this, &s](const param_data &p) { SQLLEN readed = SQL_NULL_DATA; while (!s.eof() && !s.fail()) { s.read((char *)p.m_data, p.m_size); readed = (unsigned long)s.gcount(); if (readed > 0) { verify_error(SQLPutData(m_handle, p.m_data, readed)); } } }; } void bind_param(size_t index, const blob_writer ¶m) { m_params[index].m_data = nullptr; m_params[index].m_size = blob_buffer_size; m_params[index].m_indicator = SQL_LEN_DATA_AT_EXEC(m_params[index].m_size); verify_error(SQLBindParameter(m_handle, static_cast(index + 1), SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, INT_MAX, 0, &m_params[index], 0, &m_params[index].m_indicator)); m_params[index].m_after_fetch = [this, index, ¶m](const param_data &b) { blobbuf buf; buf.open(this, static_cast(index), std::ios::out); std::ostream s(&buf); param(s); }; } void bind_field(size_t index, bool &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_BIT, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, int8_t &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_STINYINT, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, uint8_t &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_UTINYINT, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, int16_t &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_SSHORT, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, uint16_t &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_USHORT, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, int32_t &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_SLONG, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, uint32_t &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_ULONG, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, int64_t &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_SBIGINT, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, uint64_t &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_UBIGINT, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, float &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_FLOAT, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, double &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_DOUBLE, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, DATE_STRUCT &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_TYPE_DATE, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, TIME_STRUCT &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_TYPE_TIME, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, TIMESTAMP_STRUCT &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_TYPE_TIMESTAMP, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, SQLGUID &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_GUID, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, SQL_NUMERIC_STRUCT &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_NUMERIC, &v, 0, &m_params[index].m_indicator)); } void bind_field(size_t index, char *v, size_t n) { m_params[index].m_data = v; m_params[index].m_size = n; m_params[index].m_after_fetch = [](const param_data &p) { if (p.m_indicator == SQL_NULL_DATA) memset(p.m_data, 0, p.m_size * sizeof(char)); else { char *text = reinterpret_cast(p.m_data); text[p.m_indicator] = '\0'; } }; verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_CHAR, v, n, &m_params[index].m_indicator)); } void bind_field(size_t index, wchar_t *v, size_t n) { m_params[index].m_data = v; m_params[index].m_size = n; m_params[index].m_after_fetch = [](const param_data &p) { if (p.m_indicator == SQL_NULL_DATA) memset(p.m_data, 0, p.m_size * sizeof(wchar_t)); else { wchar_t *text = reinterpret_cast(p.m_data); text[p.m_indicator] = '\0'; } }; verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_WCHAR, v, n, &m_params[index].m_indicator)); } template void bind_field(size_t index, qtl::bind_string_helper &&v) { SQLLEN length = 0; verify_error(SQLColAttribute(m_handle, static_cast(index + 1), SQL_DESC_LENGTH, NULL, 0, NULL, &length)); typename qtl::bind_string_helper::char_type *data = v.alloc(length); bind_field(index, data, length + 1); m_params[index].m_after_fetch = [v](const param_data &p) mutable { if (p.m_indicator == SQL_NULL_DATA) v.clear(); else v.truncate(p.m_indicator); }; } template void bind_field(size_t index, std::array &&value) { bind_field(index, value.data(), value.size()); } template void bind_field(size_t index, std::array &&value) { bind_field(index, value.data(), value.size()); } void bind_field(size_t index, qtl::blob_data &&v) { verify_error(SQLBindCol(m_handle, static_cast(index + 1), SQL_C_BINARY, v.data, v.size, &m_params[index].m_indicator)); } void bind_field(size_t index, std::ostream &&v) { if (m_blob_buffer == NULL) m_blob_buffer = malloc(blob_buffer_size); m_params[index].m_data = m_blob_buffer; m_params[index].m_size = blob_buffer_size; m_params[index].m_after_fetch = [this, index, &v](const param_data &p) { SQLRETURN ret = SQLGetData(m_handle, static_cast(index + 1), SQL_C_BINARY, p.m_data, p.m_size, const_cast(&p.m_indicator)); while (ret != SQL_NO_DATA) { size_t n = (p.m_indicator > blob_buffer_size) || (p.m_indicator == SQL_NO_TOTAL) ? blob_buffer_size : p.m_indicator; verify_error(ret); v.write((const char *)p.m_data, n); ret = SQLGetData(m_handle, static_cast(index + 1), SQL_C_BINARY, p.m_data, p.m_size, const_cast(&p.m_indicator)); } }; } void bind_field(size_t index, blobbuf &&value) { m_params[index].m_data = nullptr; m_params[index].m_size = 0; m_params[index].m_after_fetch = [this, index, &value](const param_data &p) { value.open(this, static_cast(index), std::ios::in); }; } template void bind_field(size_t index, indicator &&value) { qtl::bind_field(*this, index, value.data); param_data ¶m = m_params[index]; auto fetch_fun = param.m_after_fetch; param.m_after_fetch = [fetch_fun, &value](const param_data &p) { value.is_truncated = false; if (p.m_indicator == SQL_NULL_DATA) { value.is_null = true; value.length = 0; } else if (p.m_indicator >= 0) { value.is_null = false; value.length = p.m_indicator; if (p.m_size > 0 && p.m_indicator >= p.m_size) value.is_truncated = true; } if (fetch_fun) fetch_fun(p); }; } #ifdef _QTL_ENABLE_CPP17 template void bind_field(size_t index, std::optional &&value) { qtl::bind_field(*this, index, *value); param_data ¶m = m_params[index]; auto fetch_fun = param.m_after_fetch; param.m_after_fetch = [fetch_fun, &value](const param_data &p) { if (fetch_fun) fetch_fun(p); if (p.m_indicator == SQL_NULL_DATA) value.reset(); }; } void bind_field(size_t index, std::any &&value) { SQLLEN type = 0, isUnsigned = SQL_FALSE; verify_error(SQLColAttribute(m_handle, index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &type)); verify_error(SQLColAttribute(m_handle, index + 1, SQL_DESC_UNSIGNED, NULL, 0, NULL, &isUnsigned)); switch (type) { case SQL_BIT: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; case SQL_TINYINT: if (isUnsigned) { value.emplace(); bind_field(index, std::forward(std::any_cast(value))); } else { value.emplace(); bind_field(index, std::forward(std::any_cast(value))); } break; case SQL_SMALLINT: if (isUnsigned) { value.emplace(); bind_field(index, std::forward(std::any_cast(value))); } else { value.emplace(); bind_field(index, std::forward(std::any_cast(value))); } break; case SQL_INTEGER: if (isUnsigned) { value.emplace(); bind_field(index, std::forward(std::any_cast(value))); } else { value.emplace(); bind_field(index, std::forward(std::any_cast(value))); } break; case SQL_BIGINT: if (isUnsigned) { value.emplace(); bind_field(index, std::forward(std::any_cast(value))); } else { value.emplace(); bind_field(index, std::forward(std::any_cast(value))); } break; case SQL_FLOAT: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; case SQL_DOUBLE: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; case SQL_NUMERIC: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; case SQL_TIME: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; case SQL_DATE: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; case SQL_TIMESTAMP: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; case SQL_INTERVAL_MONTH: case SQL_INTERVAL_YEAR: case SQL_INTERVAL_YEAR_TO_MONTH: case SQL_INTERVAL_DAY: case SQL_INTERVAL_HOUR: case SQL_INTERVAL_MINUTE: case SQL_INTERVAL_SECOND: case SQL_INTERVAL_DAY_TO_HOUR: case SQL_INTERVAL_DAY_TO_MINUTE: case SQL_INTERVAL_DAY_TO_SECOND: case SQL_INTERVAL_HOUR_TO_MINUTE: case SQL_INTERVAL_HOUR_TO_SECOND: case SQL_INTERVAL_MINUTE_TO_SECOND: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; case SQL_CHAR: value.emplace(); bind_field(index, qtl::bind_string(std::any_cast(value))); break; case SQL_GUID: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; case SQL_BINARY: value.emplace(); bind_field(index, std::forward(std::any_cast(value))); break; default: throw odbc::error(*this, SQL_ERROR); } param_data ¶m = m_params[index]; auto fetch_fun = param.m_after_fetch; param.m_after_fetch = [fetch_fun, &value](const param_data &p) { if (fetch_fun) fetch_fun(p); if (p.m_indicator == SQL_NULL_DATA) value.reset(); }; } #endif // C++17 SQLLEN affetced_rows() { SQLLEN count = 0; verify_error(SQLRowCount(m_handle, &count)); return count; } size_t find_field(const char *name) const { SQLSMALLINT count = 0; verify_error(SQLNumResultCols(m_handle, &count)); for (SQLSMALLINT i = 0; i != count; i++) { SQLCHAR field_name[256] = {0}; SQLSMALLINT name_length = 0; SQLSMALLINT data_type; SQLULEN column_size; SQLSMALLINT digits; SQLSMALLINT nullable; verify_error(SQLDescribeColA(m_handle, i, field_name, sizeof(field_name), &name_length, &data_type, &column_size, &digits, &nullable)); if (strncmp((char *)field_name, name, name_length) == 0) return i; } return -1; } void reset() { verify_error(SQLFreeStmt(m_handle, SQL_RESET_PARAMS)); } /* ODBC do not support this function, but you can use query to instead it: For MS SQL Server: SELECT @@IDENTITY; For MySQL: SELECT LAST_INSERT_ID(); For SQLite: SELECT last_insert_rowid(); */ /*uint64_t insert_id() { assert(false); return 0; }*/ protected: struct param_data { SQLPOINTER m_data; SQLLEN m_size; SQLLEN m_indicator; std::function m_after_fetch; param_data() : m_data(NULL), m_size(0), m_indicator(0) {} }; SQLPOINTER m_blob_buffer; std::vector m_params; bool m_binded_cols; }; class statement : public base_statement { public: statement() = default; explicit statement(base_database &db) : base_statement(db) {} statement(statement &&src) : base_statement(std::move(src)) {} statement &operator=(statement &&src) { base_statement::operator=(std::move(src)); return *this; } ~statement() { close(); } void open(const char *query_text, size_t text_length = SQL_NTS) { reset(); verify_error(SQLPrepareA(m_handle, (SQLCHAR *)query_text, text_length)); } void open(const std::string &query_text) { open(query_text.data(), query_text.size()); } template void execute(const Types ¶ms) { SQLSMALLINT count = 0; verify_error(SQLNumParams(m_handle, &count)); if (count > 0) { m_params.resize(count); qtl::bind_params(*this, params); } SQLRETURN ret = SQLExecute(m_handle); verify_error(ret); if (ret == SQL_NEED_DATA) { SQLPOINTER token; size_t i = 0; ret = SQLParamData(m_handle, &token); verify_error(ret); while (ret == SQL_NEED_DATA) { while (i != count) { if (&m_params[i] == token) { if (m_params[i].m_after_fetch) m_params[i].m_after_fetch(m_params[i]); break; } ++i; } ret = SQLParamData(m_handle, &token); verify_error(ret); } } } template bool fetch(Types &&values) { if (!m_binded_cols) { SQLSMALLINT count = 0; verify_error(SQLNumResultCols(m_handle, &count)); if (count > 0) { m_params.resize(count); qtl::bind_record(*this, std::forward(values)); } m_binded_cols = true; } return fetch(); } bool fetch() { SQLRETURN ret = SQLFetch(m_handle); if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { for (const param_data &data : m_params) { if (data.m_after_fetch) data.m_after_fetch(data); } return true; } verify_error(ret); return false; } bool next_result() { SQLRETURN ret; SQLSMALLINT count = 0; m_binded_cols = false; do { ret = SQLMoreResults(m_handle); if (ret == SQL_ERROR || ret == SQL_INVALID_HANDLE) verify_error(ret); verify_error(SQLNumResultCols(m_handle, &count)); } while (count == 0); return ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO; } }; struct connection_parameter { std::string m_name; std::string m_prompt; std::string m_value; std::vector m_value_list; bool m_optinal; bool m_assigned; connection_parameter() : m_optinal(false), m_assigned(false) {} void reset() { m_name.clear(); m_prompt.clear(); m_value.clear(); m_value_list.clear(); m_optinal = false; m_assigned = false; } }; typedef std::vector connection_parameters; class base_database : public object { public: typedef odbc::error exception_type; explicit base_database(environment &env) : object(env.handle()), m_opened(false) { } base_database(const base_database &) = delete; base_database(base_database &&src) : object(std::forward(src)), m_connection(std::forward(src.m_connection)) { m_opened = src.m_opened; src.m_opened = false; } ~base_database() { close(); } base_database &operator=(base_database &&src) { if (this != &src) { object::operator=(std::forward(src)); m_opened = src.m_opened; src.m_opened = false; m_connection = std::forward(src.m_connection); } return *this; } void close() { if (m_opened) { verify_error(SQLDisconnect(m_handle)); m_opened = false; } } void set_attribute(SQLINTEGER attr, SQLPOINTER value) { verify_error(SQLSetConnectAttrA(m_handle, attr, (SQLPOINTER)value, SQL_IS_POINTER)); } void set_attribute(SQLINTEGER attr, SQLINTEGER value) { verify_error(SQLSetConnectAttrA(m_handle, attr, (SQLPOINTER)value, SQL_IS_INTEGER)); } void set_attribute(SQLINTEGER attr, SQLUINTEGER value) { verify_error(SQLSetConnectAttrA(m_handle, attr, (SQLPOINTER)value, SQL_IS_UINTEGER)); } void set_attribute(SQLINTEGER attr, SQLSMALLINT value) { verify_error(SQLSetConnectAttrA(m_handle, attr, (SQLPOINTER)value, SQL_IS_SMALLINT)); } void set_attribute(SQLINTEGER attr, SQLUSMALLINT value) { verify_error(SQLSetConnectAttrA(m_handle, attr, (SQLPOINTER)value, SQL_IS_USMALLINT)); } void set_attribute(SQLINTEGER attr, const char *value) { verify_error(SQLSetConnectAttrA(m_handle, attr, (SQLPOINTER)value, SQL_NTS)); } void set_attribute(SQLINTEGER attr, const std::string &value) { verify_error(SQLSetConnectAttrA(m_handle, attr, (SQLPOINTER)value.data(), value.size())); } void set_attribute(SQLINTEGER attr, const void *value, SQLINTEGER length) { verify_error(SQLSetConnectAttrA(m_handle, attr, (SQLPOINTER)value, SQL_LEN_BINARY_ATTR(length))); } void get_attribute(SQLINTEGER attr, SQLPOINTER &value) const { verify_error(SQLGetConnectAttrA(m_handle, attr, &value, SQL_IS_POINTER, 0)); } void get_attribute(SQLINTEGER attr, SQLINTEGER &value) const { value = 0; verify_error(SQLGetConnectAttrA(m_handle, attr, &value, SQL_IS_INTEGER, 0)); } void get_attribute(SQLINTEGER attr, SQLUINTEGER &value) const { value = 0; verify_error(SQLGetConnectAttrA(m_handle, attr, &value, SQL_IS_UINTEGER, 0)); } void get_attribute(SQLINTEGER attr, SQLSMALLINT &value) const { value = 0; verify_error(SQLGetConnectAttrA(m_handle, attr, &value, SQL_IS_SMALLINT, 0)); } void get_attribute(SQLINTEGER attr, SQLUSMALLINT &value) const { value = 0; verify_error(SQLGetConnectAttrA(m_handle, attr, &value, SQL_IS_USMALLINT, 0)); } void get_attribute(SQLINTEGER attr, void *buffer, SQLINTEGER length) const { verify_error(SQLGetConnectAttrA(m_handle, attr, buffer, SQL_LEN_BINARY_ATTR(length), 0)); } void get_attribute(SQLINTEGER attr, char *buffer, size_t length) const { verify_error(SQLGetConnectAttrA(m_handle, attr, buffer, length, 0)); } void get_attribute(SQLINTEGER attr, std::string &value) const { SQLINTEGER length = 0; verify_error(SQLGetConnectAttrA(m_handle, attr, NULL, 0, &length)); value.resize(length); if (length > 0) verify_error(SQLGetConnectAttrA(m_handle, attr, (SQLPOINTER)value.data(), length, 0)); } void get_info(SQLSMALLINT info, std::string &value, SQLSMALLINT size = SQL_MAX_OPTION_STRING_LENGTH) const { value.resize(size); verify_error(SQLGetInfo(m_handle, info, (SQLPOINTER)value.data(), size, &size)); value.resize(size); } std::string dbms_name() const { std::string name; get_info(SQL_DBMS_NAME, name); return name; } std::string server_name() const { std::string name; get_info(SQL_SERVER_NAME, name); return name; } std::string user_name() const { std::string name; get_info(SQL_USER_NAME, name); return name; } std::string db_name() const { std::string name; get_info(SQL_DATABASE_NAME, name); return name; } const std::string &connection_text() const { return m_connection; } protected: bool m_opened; std::string m_connection; void parse_browse_string(const char *output_text, size_t text_length, connection_parameters ¶meters); std::string create_connection_text(const connection_parameters ¶meters); }; class database : public base_database, public qtl::base_database { public: database() = default; explicit database(environment &env) : odbc::base_database(env) { } database(database &&src) : odbc::base_database(std::move(src)) { } void open(const char *server_name, size_t server_name_length, const char *user_name, size_t user_name_length, const char *password, size_t password_length) { if (m_opened) close(); verify_error(SQLConnectA(m_handle, (SQLCHAR *)server_name, static_cast(server_name_length), (SQLCHAR *)user_name, static_cast(user_name_length), (SQLCHAR *)password, static_cast(password_length))); m_opened = true; } void open(const char *server_name, const char *user_name, const char *password) { verify_error(SQLConnectA(m_handle, (SQLCHAR *)server_name, SQL_NTS, (SQLCHAR *)user_name, SQL_NTS, (SQLCHAR *)password, SQL_NTS)); } void open(const std::string &server_name, const std::string &user_name, const std::string &password) { open(server_name.data(), server_name.size(), user_name.data(), user_name.size(), password.data(), password.size()); } void open(const char *input_text, size_t text_length = SQL_NTS, SQLSMALLINT driver_completion = SQL_DRIVER_NOPROMPT, SQLHWND hwnd = NULL) { m_connection.resize(512); SQLSMALLINT out_len = 0; if (m_opened) close(); verify_error(SQLDriverConnectA(m_handle, hwnd, (SQLCHAR *)input_text, (SQLSMALLINT)text_length, (SQLCHAR *)m_connection.data(), (SQLSMALLINT)m_connection.size(), &out_len, driver_completion)); m_connection.resize(out_len); m_opened = true; } void open(const std::string &input_text, SQLSMALLINT driver_completion = SQL_DRIVER_NOPROMPT, SQLHWND hwnd = NULL) { open(input_text.data(), input_text.size(), driver_completion, hwnd); } void open(SQLHWND hwnd, SQLSMALLINT driver_completion = SQL_DRIVER_COMPLETE) { open("", SQL_NTS, driver_completion, hwnd); } // InputPred like: // bool input_parameters(connection_parameters& parameters); template void open(const char *connection_text, size_t text_length, InputPred &&pred) { SQLSMALLINT length = 0; SQLRETURN ret = SQL_SUCCESS; std::string input_text; if (m_opened) close(); if (text_length == SQL_NTS) input_text = connection_text; else input_text.assign(connection_text, text_length); m_connection.resize(1024); while ((ret = SQLBrowseConnectA(m_handle, (SQLCHAR *)input_text.data(), SQL_NTS, (SQLCHAR *)m_connection.data(), m_connection.size(), &length)) == SQL_NEED_DATA) { connection_parameters parameters; parse_browse_string(m_connection.data(), length, parameters); if (!pred(parameters)) throw error(SQL_NEED_DATA, "User cancel operation."); input_text = create_connection_text(parameters); } if (ret == SQL_ERROR || ret == SQL_SUCCESS_WITH_INFO) verify_error(ret); m_opened = true; } template void open(const char *connection_text, InputPred &&pred) { open(connection_text, SQL_NTS, std::forward(pred)); } template void open(const std::string &connection_text, InputPred &&pred) { open(connection_text.data(), connection_text.size(), std::forward(pred)); } statement open_command(const char *query_text, size_t text_length) { statement stmt(*this); stmt.open(query_text, text_length); return stmt; } statement open_command(const char *query_text) { return open_command(query_text, strlen(query_text)); } statement open_command(const std::string &query_text) { return open_command(query_text.data(), query_text.length()); } void simple_execute(const char *query_text, size_t text_length = SQL_NTS) { statement command(*this); SQLRETURN ret = SQLExecDirectA(command.handle(), (SQLCHAR *)query_text, text_length); if (ret != SQL_SUCCESS && ret != SQL_NO_DATA) verify_error(ret); } void simple_execute(const std::string &query_text) { simple_execute(query_text.data(), query_text.size()); } void auto_commit(bool on) { set_attribute(SQL_ATTR_AUTOCOMMIT, on ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF); } void begin_transaction() { auto_commit(false); } void rollback() { verify_error(SQLEndTran(handler_type, m_handle, SQL_ROLLBACK)); auto_commit(true); } void commit() { verify_error(SQLEndTran(handler_type, m_handle, SQL_COMMIT)); auto_commit(true); } bool is_alive() { SQLINTEGER value; get_attribute(SQL_ATTR_CONNECTION_DEAD, value); return value == SQL_CD_FALSE; } #ifdef QTL_ODBC_ENABLE_ASYNC_MODE // async_connection async_mode(); #endif // ODBC 3.80 }; struct date : public SQL_DATE_STRUCT { date() { memset(this, 0, sizeof(SQL_DATE_STRUCT)); } }; struct time : public SQL_TIME_STRUCT { time() { memset(this, 0, sizeof(SQL_TIME_STRUCT)); } }; struct timestamp : public SQL_TIMESTAMP_STRUCT { timestamp() { memset(this, 0, sizeof(SQL_TIMESTAMP_STRUCT)); } timestamp(struct tm &tm) { year = tm.tm_year + 1900; month = tm.tm_mon + 1; day = tm.tm_mday; hour = tm.tm_hour; minute = tm.tm_min; second = tm.tm_sec; } timestamp(time_t value) { struct tm tm; #if defined(_MSC_VER) localtime_s(&tm, &value); #elif defined(_POSIX_VERSION) localtime_r(&value, &tm); #else tm = *localtime(&value); #endif new (this) timestamp(tm); } timestamp(const timestamp &src) { memcpy(this, &src, sizeof(SQL_TIMESTAMP_STRUCT)); } timestamp &operator=(const timestamp &src) { if (this != &src) memcpy(this, &src, sizeof(SQL_TIMESTAMP_STRUCT)); return *this; } static timestamp now() { time_t value; ::time(&value); return timestamp(value); } time_t as_tm(struct tm &tm) const { tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = minute; tm.tm_sec = second; return mktime(&tm); } time_t get_time() const { struct tm tm; return as_tm(tm); } timeval get_timeval() const { timeval tv; struct tm tm; tv.tv_sec = as_tm(tm); tv.tv_usec = fraction / 1000; } }; #ifdef QTL_ODBC_ENABLE_ASYNC_MODE class async_connection; inline bool is_still_executing(SQLINTEGER code) { return code == SQL_STILL_EXECUTING; } class async_statement : public base_statement { public: explicit async_statement(async_connection &db); async_statement(async_statement &&src) : base_statement(std::move(src)) { m_hCompleteEvent = src.m_hCompleteEvent; m_event = src.m_event; m_nQueryTimeout = src.m_nQueryTimeout; src.m_hCompleteEvent = nullptr; src.m_event = nullptr; } async_statement &operator=(async_statement &&src) { if (this != &src) { base_statement::operator=(std::move(src)); m_hCompleteEvent = src.m_hCompleteEvent; m_event = src.m_event; m_nQueryTimeout = src.m_nQueryTimeout; src.m_hCompleteEvent = nullptr; src.m_event = nullptr; } return *this; } ~async_statement() { close(); } /* Handler defiens as: void handler(const qtl::odbc::error& e); */ template void open(Handler &&handler, const char *query_text, size_t text_length = 0) { if (text_length == 0) text_length = strlen(query_text); reset(); SQLRETURN ret = SQLPrepareA(m_handle, (SQLCHAR *)query_text, text_length); async_wait(ret, std::forward(handler)); } /* ExecuteHandler defiens as: void handler(const qtl::odbc::error& e, uint64_t affected); */ template void execute(const Types ¶ms, Handler &&handler) { SQLSMALLINT count = 0; SQLRETURN ret = SQLNumParams(m_handle, &count); if (!SQL_SUCCEEDED(ret)) { handler(error(*this, ret), 0); return; } if (count > 0) { m_params.resize(count); qtl::bind_params(*this, params); } if (m_nQueryTimeout == 0) m_nQueryTimeout = query_timeout(); ret = SQLExecute(m_handle); async_wait(ret, [this, count, handler](const error &e) mutable { SQLINTEGER ret = e.code(); if (ret == SQL_NEED_DATA) async_param_data(0, count, std::forward(handler)); else if(ret>=0) handler(error(*this, ret), affetced_rows()); else handler(error(*this, ret), 0); }); } template void fetch(Types &&values, RowHandler &&row_handler, FinishHandler &&finish_handler) { if (!m_binded_cols) { SQLSMALLINT count = 0; SQLRETURN ret = SQLNumResultCols(m_handle, &count); if (!SQL_SUCCEEDED(ret)) { finish_handler(error(*this, ret)); return; } if (count > 0) { m_params.resize(count); qtl::bind_record(*this, std::forward(values)); } m_binded_cols = true; } return fetch(std::forward(row_handler), std::forward(finish_handler)); } template void fetch(RowHandler &&row_handler, FinishHandler &&finish_handler) { SQLRETURN ret = SQLFetch(m_handle); async_wait(ret, [this, row_handler, finish_handler](const error &e) mutable { SQLINTEGER ret = e.code(); if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { for (const param_data& data : m_params) { if (data.m_after_fetch) data.m_after_fetch(data); } if (row_handler()) fetch(row_handler, finish_handler); else finish_handler(error()); } else { if (e.code() == SQL_NO_DATA) finish_handler(error()); else finish_handler(e); } }); } template void next_result(Handler handler) { SQLRETURN ret; m_binded_cols = false; ret = SQLMoreResults(m_handle); async_wait(ret, [this, handler](const error &e) mutable { SQLINTEGER ret=e.code(); SQLSMALLINT count = 0; if (ret == SQL_ERROR || ret == SQL_INVALID_HANDLE) { reset(); handler(error(*this, ret)); return; } ret = SQLNumResultCols(m_handle, &count); if (ret == SQL_ERROR || ret == SQL_INVALID_HANDLE) { reset(); handler(error(*this, ret)); return; } if (count > 0) handler(error()); else next_result(handler); }); } HANDLE event_handle() const { return m_hCompleteEvent; } void close() { close_event(); base_statement::close(); } template void close(CloseHandler &&handler) { if (m_handle) { close_event(); SQLRETURN ret = SQLFreeHandle(handler_type, m_handle); if (SQL_SUCCEEDED(ret)) m_handle = SQL_NULL_HANDLE; handler(error(*this, ret)); } else { handler(error()); } } private: void close_event() { if (m_hCompleteEvent) { if (m_event) m_event->remove(); verify_error(SQLCancelHandle(handler_type, m_handle)); verify_error(SQLSetStmtAttr(m_handle, SQL_ATTR_ASYNC_STMT_EVENT, NULL, SQL_IS_POINTER)); verify_error(SQLSetStmtAttr(m_handle, SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_OFF, SQL_IS_INTEGER)); CloseHandle(m_hCompleteEvent); m_hCompleteEvent = NULL; } } template void async_wait(SQLINTEGER ret, Handler &&handler) NOEXCEPT { if (is_still_executing(ret)) { m_event->set_io_handler(0, m_nQueryTimeout, [this, handler](int flags) mutable { RETCODE code; SQLCompleteAsync(SQL_HANDLE_STMT, m_handle, &code); if (SQL_SUCCEEDED(code)) { handler(odbc::error()); } else { SetEvent(m_hCompleteEvent); handler(odbc::error(*this, code)); } }); } else { handler(error(*this, ret)); } } template void async_param_data(SQLSMALLINT index, SQLSMALLINT count, Handler &&handler) NOEXCEPT { SQLPOINTER token; SQLRETURN ret = SQLParamData(m_handle, &token); async_wait(ret, [this, index, count, token, handler](const error &e) mutable { SQLINTEGER ret = e.code(); if (ret == SQL_NEED_DATA) { while (index != count) { if (&m_params[index] == token) { if (m_params[index].m_after_fetch) m_params[index].m_after_fetch(m_params[index]); break; } ++index; } async_param_data(index, count, handler); } else { handler(error(*this, ret), affetced_rows()); } }); } int query_timeout() const { SQLULEN timeout = 0; verify_error(SQLGetStmtAttr(m_handle, SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER)&timeout, NULL, NULL)); return timeout; } private: HANDLE m_hCompleteEvent; qtl::event *m_event; SQLULEN m_nQueryTimeout; }; class async_connection : public base_database, public qtl::async_connection { public: async_connection(environment &env) : base_database(env) { set_attribute(SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, SQL_ASYNC_DBC_ENABLE_ON); m_hCompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_hCompleteEvent == NULL) { throw std::system_error(std::error_code(GetLastError(), std::system_category())); } set_attribute(SQL_ATTR_ASYNC_DBC_EVENT, m_hCompleteEvent); } async_connection(async_connection &&src) : base_database(std::move(src)), qtl::async_connection(std::move(src)), m_BindFunc(std::move(src.m_BindFunc)) { m_hCompleteEvent = src.m_hCompleteEvent; src.m_hCompleteEvent = nullptr; } ~async_connection() { if (m_hCompleteEvent) { verify_error(SQLCancelHandle(handler_type, m_handle)); set_attribute(SQL_ATTR_ASYNC_DBC_EVENT, (SQLPOINTER)NULL); } if (m_opened) { set_attribute(SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, SQL_ASYNC_DBC_ENABLE_OFF); verify_error(SQLDisconnect(m_handle)); m_opened = false; } if (m_hCompleteEvent) { CloseHandle(m_hCompleteEvent); } } /* OpenHandler defines as: void handler(const qtl::odbc::error& e) NOEXCEPT; */ template void open(EventLoop &ev, OpenHandler &&handler, const char *server_name, size_t server_name_length, const char *user_name, size_t user_name_length, const char *password, size_t password_length) { if (m_opened) close(); SQLRETURN err = SQLConnectA(m_handle, (SQLCHAR *)server_name, static_cast(server_name_length), (SQLCHAR *)user_name, static_cast(user_name_length), (SQLCHAR *)password, static_cast(password_length)); async_wait_connect(err, ev, std::forward(handler)); } template void open(EventLoop &ev, OpenHandler &&handler, const char *server_name, const char *user_name, const char *password) { if (m_opened) close(); SQLRETURN err = SQLConnectA(m_handle, (SQLCHAR *)server_name, SQL_NTS, (SQLCHAR *)user_name, SQL_NTS, (SQLCHAR *)password, SQL_NTS); async_wait_connect(err, ev, std::forward(handler)); } template void open(EventLoop &ev, OpenHandler &&handler, const std::string &server_name, const std::string &user_name, const std::string &password) { open(ev, std::forward(handler), server_name.data(), server_name.size(), user_name.data(), user_name.size(), password.data(), password.size()); } template void open(EventLoop &ev, OpenHandler &&handler, const char *input_text, size_t text_length = SQL_NTS, SQLSMALLINT driver_completion = SQL_DRIVER_NOPROMPT, SQLHWND hwnd = NULL) { m_connection.resize(512); SQLSMALLINT out_len = 0; SQLRETURN err = SQLDriverConnectA(m_handle, hwnd, (SQLCHAR *)input_text, (SQLSMALLINT)text_length, (SQLCHAR *)m_connection.data(), (SQLSMALLINT)m_connection.size(), &out_len, driver_completion); m_connection.resize(out_len); async_wait_connect(err, ev, std::forward(handler)); } template void open(EventLoop &ev, OpenHandler &&handler, const std::string &input_text, SQLSMALLINT driver_completion = SQL_DRIVER_NOPROMPT, SQLHWND hwnd = NULL) { open(ev, std::forward(handler), input_text.data(), input_text.size(), driver_completion, hwnd); } template void open(EventLoop &ev, OpenHandler &&handler, SQLHWND hwnd, SQLSMALLINT driver_completion = SQL_DRIVER_COMPLETE) { open(ev, std::forward(handler), "", SQL_NTS, driver_completion, hwnd); } /* CloseHandler defines as: void handler(const qtl::odbc::error& e) NOEXCEPT; */ template void close(CloseHandler &&handler) NOEXCEPT { SQLRETURN ret = SQLDisconnect(m_handle); m_opened = false; async_wait(ret, [this, handler](const error &e) { if (!e) m_opened = false; handler(e); }); } /* ExecuteHandler defines as: void handler(const qtl::odbc::error& e) NOEXCEPT; */ template void simple_execute(ExecuteHandler &&handler, const char *query_text, size_t text_length = SQL_NTS) NOEXCEPT { statement command(*this); SQLRETURN ret = SQLExecDirectA(command.handle(), (SQLCHAR *)query_text, static_cast(text_length)); async_wait(ret, std::forward(handler)); } template void simple_execute(ExecuteHandler &&handler, const std::string &query_text) { simple_execute(std::forward(handler), query_text.data(), query_text.size()); } template void open_command(const char *query_text, size_t text_length, Handler &&handler) { std::shared_ptr stmt = std::make_shared(*this); stmt->open([stmt, handler](const odbc::error &e) mutable { handler(e, stmt); }, query_text, text_length); } HANDLE event_handle() const { return m_hCompleteEvent; } qtl::event *rebind(HANDLE hEvent) { return m_BindFunc(hEvent); } private: template void async_wait(SQLRETURN ret, Handler &&handler) NOEXCEPT { if (is_still_executing(ret)) { m_event_handler->set_io_handler(0, connect_timeout(), [this, handler](int flags) mutable { RETCODE code; SQLCompleteAsync(SQL_HANDLE_DBC, m_handle, &code); if (SQL_SUCCEEDED(code)) { handler(odbc::error()); } else { SetEvent(m_hCompleteEvent); handler(odbc::error(*this, code)); } }); } else { handler(odbc::error(*this, ret)); } } template void async_wait_connect(SQLRETURN err, EventLoop &ev, Handler &&handler) { bind(ev); m_BindFunc = [&ev](HANDLE hEvent) { return ev.add(hEvent); }; if (is_still_executing(err)) { async_wait(err, [this, handler](const error &e) mutable { if (!e) m_opened = true; handler(e); }); } else { handler(odbc::error(*this, err)); } } int connect_timeout() const { int timeout = 0; verify_error(SQLGetConnectAttr(m_handle, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)&timeout, 0, NULL)); return timeout; } private: HANDLE m_hCompleteEvent; std::function m_BindFunc; }; inline async_statement::async_statement(async_connection &db) : base_statement(static_cast(db)) { verify_error(SQLSetStmtAttr(m_handle, SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_ON, SQL_IS_INTEGER)); m_hCompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_hCompleteEvent == NULL) { throw std::system_error(std::error_code(GetLastError(), std::system_category())); } verify_error(SQLSetStmtAttr(m_handle, SQL_ATTR_ASYNC_STMT_EVENT, m_hCompleteEvent, SQL_IS_POINTER)); m_event = db.rebind(this->m_hCompleteEvent); m_nQueryTimeout = query_timeout(); } #endif // ODBC 3.80 typedef qtl::transaction transaction; template using query_iterator = qtl::query_iterator; template using query_result = qtl::query_result; template inline statement &operator<<(statement &stmt, const Params ¶ms) { stmt.reset(); stmt.execute(params); return stmt; } template inline error::error(const object &h, SQLINTEGER code) { m_errno = code; if (code == SQL_ERROR || code == SQL_SUCCESS_WITH_INFO) { SQLSMALLINT i = 0; SQLINTEGER err = SQL_SUCCESS; SQLCHAR message[SQL_MAX_MESSAGE_LENGTH]; SQLCHAR state[SQL_SQLSTATE_SIZE + 1]; std::ostringstream oss; SQLRETURN ret = SQLGetDiagRecA(object::handler_type, h.handle(), ++i, state, &err, message, SQL_MAX_MESSAGE_LENGTH, NULL); while (ret == SQL_SUCCESS) { oss << "[" << state << "] (" << err << ") " << message << std::endl; ret = SQLGetDiagRecA(object::handler_type, h.handle(), ++i, state, &err, message, SQL_MAX_MESSAGE_LENGTH, NULL); } m_errmsg = oss.str(); } else if (code == SQL_INVALID_HANDLE) { m_errmsg = "Invalid handle."; } } inline void base_database::parse_browse_string(const char *output_text, size_t text_length, connection_parameters ¶meters) { enum { part_name, part_prompt, part_list, part_value }; const char *sp = output_text; const char *token = sp; connection_parameter parameter; int part_type = part_name; while (sp != output_text + text_length) { switch (*sp) { case ';': parameters.emplace_back(parameter); parameter.reset(); part_type = part_name; token = sp + 1; break; case '=': if (part_type == part_prompt) parameter.m_prompt.assign(token, sp - token); part_type = part_value; token = sp + 1; break; case ':': if (part_type == part_name) parameter.m_name.assign(token, sp - token); part_type = part_prompt; token = sp + 1; break; case '{': part_type = part_list; parameter.m_value_list.clear(); token = sp + 1; break; ; case '}': case ',': if (part_type == part_list) parameter.m_value_list.emplace_back(token, sp - token); token = sp + 1; break; case '*': if (part_type == part_name && token == sp) { parameter.m_optinal = true; token = sp + 1; } break; case '?': token = sp + 1; break; } ++sp; } if (!parameter.m_name.empty()) parameters.emplace_back(parameter); } inline std::string base_database::create_connection_text(const connection_parameters ¶meters) { std::ostringstream oss; for (auto ¶meter : parameters) { if (parameter.m_assigned) oss << parameter.m_name << '=' << parameter.m_value << ';'; } return oss.str(); } inline base_statement::base_statement(base_database &db) : object(db.handle()), m_blob_buffer(NULL), m_binded_cols(false) { } } // odbc #ifdef _WIN32 namespace mssql { class database : public odbc::database { public: explicit database(odbc::environment &env) : odbc::database(env) {} database(database &&src) : odbc::database(std::move(src)) {} void open(const char *server, const char *db = NULL, const char *user = NULL, const char *password = NULL) { std::ostringstream oss; oss << "DRIVER={SQL Server};SERVER=" << server << ";"; if (user == NULL) oss << "UID=;PWD=;Trusted_Connection=yes;"; else { oss << "UID=" << user << ";PWD="; if (password) oss << password; oss << ";Trusted_Connection=no;"; } oss << "DATABASE=" << db; odbc::database::open(oss.str()); } }; #ifdef QTL_ODBC_ENABLE_ASYNC_MODE class async_connection : public odbc::async_connection { public: explicit async_connection(odbc::environment &env) : odbc::async_connection(env) {} async_connection(async_connection &&src) : odbc::async_connection(std::move(src)) {} template void open(EventLoop &ev, const OpenHandler &handler, const char *server, const char *db = NULL, const char *user = NULL, const char *password = NULL) { std::ostringstream oss; oss << "DRIVER={ODBC Driver 11 for SQL Server};SERVER=" << server << ";"; if (user == NULL) oss << "UID=;PWD=;Trusted_Connection=yes;"; else { oss << "UID=" << user << ";PWD="; if (password) oss << password; oss << ";Trusted_Connection=no;"; } oss << "DATABASE=" << db; odbc::async_connection::open(ev, handler, oss.str()); } }; #endif } // mssql namespace msaccess { class database : public odbc::database { public: explicit database(odbc::environment &env) : odbc::database(env) {} database(database &&src) : odbc::database(std::forward(src)) {} void open(const char *filename, const char *user = NULL, const char *password = NULL) { std::ostringstream oss; oss << "DRIVER={Microsoft Access Driver};DBQ=" << filename; if (user) oss << ";UID:" << user; if (password) oss << ";PWD=" << password; odbc::database::open(oss.str()); } }; } // msaccess #endif //_WIN32 } #endif //_QTL_ODCB_H_