driver/driver.h (1,021 lines of code) (raw):

// Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Copyright (c) 2001, 2024, Oracle and/or its affiliates. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License, version 2.0, as // published by the Free Software Foundation. // // This program is designed to work with certain software (including // but not limited to OpenSSL) that is licensed under separate terms, as // designated in a particular file or component or in included license // documentation. The authors of MySQL hereby grant you an additional // permission to link the program and your derivative works with the // separately licensed software that they have either included with // the program or referenced in the documentation. // // Without limiting anything contained in the foregoing, this file, // which is part of Connector/ODBC, is also subject to the // Universal FOSS Exception, version 1.0, a copy of which can be found at // https://oss.oracle.com/licenses/universal-foss-exception. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License, version 2.0, for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA /** @file driver.h @brief Definitions needed by the driver */ #ifndef __DRIVER_H__ #define __DRIVER_H__ #include <atomic> #include <ctpl_stl.h> #include "../MYODBC_MYSQL.h" #include "../MYODBC_CONF.h" #include "../MYODBC_ODBC.h" #include "telemetry.h" #include "util/installer.h" #include "connection_handler.h" #include "connection_proxy.h" #include "topology_service.h" #include "failover.h" /* Disable _attribute__ on non-gcc compilers. */ #if !defined(__attribute__) && !defined(__GNUC__) # define __attribute__(arg) #endif #ifdef APSTUDIO_READONLY_SYMBOLS #define WIN32 /* Hack for rc files */ #endif /* Needed for offsetof() CPP macro */ #include <stddef.h> #ifdef RC_INVOKED #define stdin #endif /* Misc definitions for AIX .. */ #ifndef crid_t typedef int crid_t; #endif #ifndef class_id_t typedef unsigned int class_id_t; #endif #include "error.h" #include "parse.h" #include <vector> #include <list> #include <mutex> #define LOCK_STMT(S) CHECK_HANDLE(S); \ std::unique_lock<std::recursive_mutex> slock(((STMT*)S)->lock) #define LOCK_STMT_DEFER(S) CHECK_HANDLE(S); \ std::unique_lock<std::recursive_mutex> slock(((STMT*)S)->lock, std::defer_lock) #define DO_LOCK_STMT() slock.lock(); #define LOCK_DBC(D) std::unique_lock<std::recursive_mutex> dlock(((DBC*)D)->lock) #define LOCK_DBC_DEFER(D) std::unique_lock<std::recursive_mutex> dlock(((DBC*)D)->lock, std::defer_lock) #define DO_LOCK_DBC() dlock.lock(); #define LOCK_ENV(E) std::unique_lock<std::mutex> elock(E->lock) // SQL_DRIVER_CONNECT_ATTR_BASE is not defined in all driver managers. // Therefore use a custom constant until it becomes a standard. #define MYSQL_DRIVER_CONNECT_ATTR_BASE 0x00004000 #define CB_FIDO_GLOBAL MYSQL_DRIVER_CONNECT_ATTR_BASE + 0x00001000 #define CB_FIDO_CONNECTION MYSQL_DRIVER_CONNECT_ATTR_BASE + 0x00001001 #if defined(_WIN32) || defined(WIN32) # define INTFUNC __stdcall # define EXPFUNC __stdcall # if !defined(HAVE_LOCALTIME_R) # define HAVE_LOCALTIME_R 1 # endif #else # define INTFUNC PASCAL # define EXPFUNC __export CALLBACK /* Simple macros to make dl look like the Windows library funcs. */ # define HMODULE void* # define LoadLibrary(library) dlopen((library), RTLD_GLOBAL | RTLD_LAZY) # define GetProcAddress(module, proc) dlsym((module), (proc)) # define FreeLibrary(module) dlclose((module)) #endif #define ODBC_DRIVER "ODBC " MYODBC_STRSERIES " Driver" #define DRIVER_NAME "AWS ODBC " MYODBC_STRSERIES " Driver For MySQL" #define DRIVER_NONDSN_TAG "DRIVER={AWS ODBC " MYODBC_STRSERIES " Driver For MySQL}" #if defined(__APPLE__) #define DRIVER_LOG_FILE "/tmp/myodbc.log" #elif defined(__UNIX__) #define DRIVER_LOG_FILE "/tmp/myodbc.log" #else #define DRIVER_LOG_FILE "myodbc.log" #endif /* Internal driver definitions */ /* Options for SQLFreeStmt */ #define FREE_STMT_RESET_BUFFERS 1000 #define FREE_STMT_RESET 1001 #define FREE_STMT_CLEAR_RESULT 1 #define FREE_STMT_DO_LOCK 2 #define MYSQL_3_21_PROTOCOL 10 /* OLD protocol */ #define CHECK_IF_ALIVE 1800 /* Seconds between queries for ping */ #define MYSQL_MAX_CURSOR_LEN 18 /* Max cursor name length */ #define MYSQL_STMT_LEN 1024 /* Max statement length */ #define MYSQL_STRING_LEN 1024 /* Max string length */ #define MYSQL_MAX_SEARCH_STRING_LEN NAME_LEN+10 /* Max search string length */ /* Max Primary keys in a cursor * WHERE clause */ #define MY_MAX_PK_PARTS 32 #ifndef NEAR #define NEAR #endif /* We don't make any assumption about what the default may be. */ #ifndef DEFAULT_TXN_ISOLATION # define DEFAULT_TXN_ISOLATION 0 #endif /* For compatibility with old mysql clients - defining error */ #ifndef ER_MUST_CHANGE_PASSWORD_LOGIN # define ER_MUST_CHANGE_PASSWORD_LOGIN 1820 #endif #ifndef CR_AUTH_PLUGIN_CANNOT_LOAD_ERROR # define CR_AUTH_PLUGIN_CANNOT_LOAD_ERROR 2059 #endif #ifndef SQL_PARAM_DATA_AVAILABLE # define SQL_PARAM_DATA_AVAILABLE 101 #endif /* Connection flags to validate after the connection*/ #define CHECK_AUTOCOMMIT_ON 1 /* AUTOCOMMIT_ON */ #define CHECK_AUTOCOMMIT_OFF 2 /* AUTOCOMMIT_OFF */ /* implementation or application descriptor? */ typedef enum { DESC_IMP, DESC_APP } desc_ref_type; /* parameter or row descriptor? */ typedef enum { DESC_PARAM, DESC_ROW, DESC_UNKNOWN } desc_desc_type; /* header or record field? (location in descriptor) */ typedef enum { DESC_HDR, DESC_REC } fld_loc; typedef void (*fido_callback_func)(const char*); extern fido_callback_func global_fido_callback; extern std::mutex global_fido_mutex; /* permissions - header, and base for record */ #define P_RI 1 /* imp */ #define P_WI 2 #define P_RA 4 /* app */ #define P_WA 8 /* macros to encode the constants above */ #define P_ROW(P) (P) #define P_PAR(P) ((P) << 4) #define PR_RIR P_ROW(P_RI) #define PR_WIR (P_ROW(P_WI) | PR_RIR) #define PR_RAR P_ROW(P_RA) #define PR_WAR (P_ROW(P_WA) | PR_RAR) #define PR_RIP P_PAR(P_RI) #define PR_WIP (P_PAR(P_WI) | PR_RIP) #define PR_RAP P_PAR(P_RI) #define PR_WAP (P_PAR(P_WA) | PR_RAP) /* macros to test type */ #define IS_APD(d) ((d)->desc_type == DESC_PARAM && (d)->ref_type == DESC_APP) #define IS_IPD(d) ((d)->desc_type == DESC_PARAM && (d)->ref_type == DESC_IMP) #define IS_ARD(d) ((d)->desc_type == DESC_ROW && (d)->ref_type == DESC_APP) #define IS_IRD(d) ((d)->desc_type == DESC_ROW && (d)->ref_type == DESC_IMP) /* additional field types needed, but not defined in ODBC */ #define SQL_IS_ULEN (-9) #define SQL_IS_LEN (-10) /* check if ARD record is a bound column */ #define ARD_IS_BOUND(d) (d)&&((d)->data_ptr || (d)->octet_length_ptr) /* get the dbc from a descriptor */ #define DESC_GET_DBC(X) (((X)->alloc_type == SQL_DESC_ALLOC_USER) ? \ (X)->dbc : (X)->stmt->dbc) #define IS_BOOKMARK_VARIABLE(S) if (S->stmt_options.bookmarks != \ SQL_UB_VARIABLE) \ { \ stmt->set_error("HY092", "Invalid attribute identifier", 0); \ return SQL_ERROR; \ } /* data-at-exec type */ #define DAE_NORMAL 1 /* normal SQLExecute() */ #define DAE_SETPOS_INSERT 2 /* SQLSetPos() insert */ #define DAE_SETPOS_UPDATE 3 /* SQLSetPos() update */ /* data-at-exec handling done for current SQLSetPos() call */ #define DAE_SETPOS_DONE 10 #define DONT_USE_LOCALE_CHECK(STMT) if (!STMT->dbc->ds->opt_NO_LOCALE) #if defined _WIN32 #define DECLARE_LOCALE_HANDLE int loc = 0; #define __LOCALE_SET(LOC) \ { \ loc = _configthreadlocale(0); \ _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); \ setlocale(LC_NUMERIC, LOC); /* force use of '.' as decimal point */ \ } #define __LOCALE_RESTORE() \ { \ setlocale(LC_NUMERIC, default_locale.c_str()); \ _configthreadlocale(loc); \ } #elif defined LC_GLOBAL_LOCALE #define DECLARE_LOCALE_HANDLE locale_t nloc; #define __LOCALE_SET(LOC) \ { \ nloc = newlocale(LC_CTYPE_MASK, LOC, (locale_t)0); \ uselocale(nloc); \ } #define __LOCALE_RESTORE() \ { \ uselocale(LC_GLOBAL_LOCALE); \ freelocale(nloc); \ } #else #define DECLARE_LOCALE_HANDLE #define __LOCALE_SET(LOC) \ { \ setlocale(LC_NUMERIC, LOC); /* force use of '.' as decimal point */ \ } #define __LOCALE_RESTORE() \ { \ setlocale(LC_NUMERIC, default_locale.c_str()); \ } #endif #define C_LOCALE_SET(STMT) \ DONT_USE_LOCALE_CHECK(STMT) \ __LOCALE_SET("C") #define DEFAULT_LOCALE_SET(STMT) \ DONT_USE_LOCALE_CHECK(STMT) \ __LOCALE_RESTORE() struct Srv_host_detail { std::string name; unsigned int port = MYSQL_PORT; }; std::vector<Srv_host_detail> parse_host_list(const char *hosts_str, unsigned int default_port); std::shared_ptr<HOST_INFO> get_host_info_from_ds(DataSource* ds); typedef struct { int perms; SQLSMALLINT data_type; /* SQL_IS_SMALLINT, etc */ fld_loc loc; size_t offset; /* offset of field in struct */ } desc_field; /* descriptor */ struct STMT; struct DESCREC{ /* ODBC spec fields */ SQLINTEGER auto_unique_value; /* row only */ SQLCHAR * base_column_name; /* row only */ SQLCHAR * base_table_name; /* row only */ SQLINTEGER case_sensitive; /* row only */ SQLCHAR * catalog_name; /* row only */ SQLSMALLINT concise_type; SQLPOINTER data_ptr; SQLSMALLINT datetime_interval_code; SQLINTEGER datetime_interval_precision; SQLLEN display_size; /* row only */ SQLSMALLINT fixed_prec_scale; SQLLEN * indicator_ptr; SQLCHAR * label; /* row only */ SQLULEN length; SQLCHAR * literal_prefix; /* row only */ SQLCHAR * literal_suffix; /* row only */ SQLCHAR * local_type_name; SQLCHAR * name; SQLSMALLINT nullable; SQLINTEGER num_prec_radix; SQLLEN octet_length; SQLLEN *octet_length_ptr; SQLSMALLINT parameter_type; /* param only */ SQLSMALLINT precision; SQLSMALLINT rowver; SQLSMALLINT scale; SQLCHAR * schema_name; /* row only */ SQLSMALLINT searchable; /* row only */ SQLCHAR * table_name; /* row only */ SQLSMALLINT type; SQLCHAR * type_name; SQLSMALLINT unnamed; SQLSMALLINT is_unsigned; SQLSMALLINT updatable; /* row only */ desc_desc_type m_desc_type; desc_ref_type m_ref_type; /* internal descriptor fields */ /* parameter-specific */ struct par_struct { /* value, value_length, and alloced are used for data * at exec parameters */ tempBuf tempbuf; /* this parameter is data-at-exec. this is needed as cursor updates in ADO change the bind_offset_ptr between SQLSetPos() and the final call to SQLParamData() which makes it impossible for us to know any longer it was a data-at-exec param. */ char is_dae; //my_bool alloced; /* Whether this parameter has been bound by the application * (if not, was created by dummy execution) */ my_bool real_param_done; par_struct() : tempbuf(0), is_dae(0), real_param_done(false) {} par_struct(const par_struct& p) : tempbuf(p.tempbuf), is_dae(p.is_dae), real_param_done(p.real_param_done) { } void add_param_data(const char *chunk, unsigned long length); size_t val_length() { // Return the current position, not the buffer length return tempbuf.cur_pos; } char *val() { return tempbuf.buf; } void reset() { tempbuf.reset(); is_dae = 0; } }par; /* row-specific */ struct row_struct{ MYSQL_FIELD * field; /* Used *only* by IRD */ ulong datalen; /* actual length, maintained for *each* row */ /* TODO ugly, but easiest way to handle memory */ SQLCHAR type_name[40]; row_struct() : field(nullptr), datalen(0) {} void reset() { field = nullptr; datalen = 0; type_name[0] = 0; } }row; void desc_rec_init_apd(); void desc_rec_init_ipd(); void desc_rec_init_ard(); void desc_rec_init_ird(); void reset_to_defaults(); DESCREC(desc_desc_type desc_type, desc_ref_type ref_type) : auto_unique_value(0), base_column_name(nullptr), base_table_name(nullptr), case_sensitive(0), catalog_name(nullptr), concise_type(0), data_ptr(nullptr), datetime_interval_code(0), datetime_interval_precision(0), display_size(0), fixed_prec_scale(0), indicator_ptr(nullptr), label(nullptr), length(0), literal_prefix(nullptr), literal_suffix(nullptr), local_type_name(nullptr), name(nullptr), nullable(0), num_prec_radix(0), octet_length(0), octet_length_ptr(nullptr), parameter_type(0), precision(0), rowver(0), scale(0), schema_name(nullptr), searchable(0), table_name(nullptr), type(0), type_name(nullptr), unnamed(0), is_unsigned(0), updatable(0), m_desc_type(desc_type), m_ref_type(ref_type) { reset_to_defaults(); } }; struct STMT; struct DBC; struct DESC { /* header fields */ SQLSMALLINT alloc_type = 0; SQLULEN array_size = 0; SQLUSMALLINT *array_status_ptr = nullptr; /* NOTE: This field is defined as SQLINTEGER* in the descriptor * documentation, but corresponds to SQL_ATTR_ROW_BIND_OFFSET_PTR or * SQL_ATTR_PARAM_BIND_OFFSET_PTR when set via SQLSetStmtAttr(). The * 64-bit ODBC addendum says that when set via SQLSetStmtAttr(), this * is now a 64-bit value. These two are conflicting, so we opt for * the 64-bit value. */ SQLULEN *bind_offset_ptr = nullptr; SQLINTEGER bind_type = 0; SQLLEN count = 0; // Only used for SQLGetDescField() SQLLEN bookmark_count = 0; /* Everywhere(http://msdn.microsoft.com/en-us/library/ms713560(VS.85).aspx http://msdn.microsoft.com/en-us/library/ms712631(VS.85).aspx) I found it's referred as SQLULEN* */ SQLULEN *rows_processed_ptr = nullptr; /* internal fields */ desc_desc_type desc_type = DESC_PARAM; desc_ref_type ref_type = DESC_IMP; std::vector<DESCREC> bookmark2; std::vector<DESCREC> records2; MYERROR error; STMT *stmt; DBC *dbc; void free_paramdata(); void reset(); SQLRETURN set_field(SQLSMALLINT recnum, SQLSMALLINT fldid, SQLPOINTER val, SQLINTEGER buflen); DESC(STMT *p_stmt, SQLSMALLINT p_alloc_type, desc_ref_type p_ref_type, desc_desc_type p_desc_type); size_t rcount() { count = (SQLLEN)records2.size(); return count; } ~DESC() { //if (desc_type == DESC_PARAM && ref_type == DESC_APP) // free_paramdata(); } /* SQL_DESC_ALLOC_USER-specific */ std::list<STMT*> stmt_list; void stmt_list_remove(STMT * p_stmt) { if (alloc_type == SQL_DESC_ALLOC_USER) stmt_list.remove(p_stmt); } void stmt_list_add(STMT *p_stmt) { if (alloc_type == SQL_DESC_ALLOC_USER) stmt_list.emplace_back(p_stmt); } inline bool is_apd() { return desc_type == DESC_PARAM && ref_type == DESC_APP; } inline bool is_ipd() { return desc_type == DESC_PARAM && ref_type == DESC_IMP; } inline bool is_ard() { return desc_type == DESC_ROW && ref_type == DESC_APP; } inline bool is_ird() { return desc_type == DESC_ROW && ref_type == DESC_IMP; } SQLRETURN set_error(char *state, const char *message, uint errcode); }; /* Statement attributes */ struct STMT_OPTIONS { SQLUINTEGER cursor_type = 0; SQLUINTEGER simulateCursor = 0; SQLULEN max_length = 0, max_rows = 0; SQLULEN query_timeout = -1; SQLUSMALLINT *rowStatusPtr_ex = nullptr; /* set by SQLExtendedFetch */ bool retrieve_data = true; SQLUINTEGER bookmarks = 0; void *bookmark_ptr = nullptr; bool bookmark_insert = false; }; /* Environment handler */ struct ENV { SQLINTEGER odbc_ver; std::list<DBC*> conn_list; MYERROR error; std::mutex lock; ctpl::thread_pool failover_thread_pool; ctpl::thread_pool custom_endpoint_thread_pool; ENV(SQLINTEGER ver) : odbc_ver(ver) {} void add_dbc(DBC* dbc); void remove_dbc(DBC* dbc); bool has_connections(); ~ENV() {} }; static std::atomic_ulong last_dbc_id{ 1 }; /* Connection handler */ struct DBC { ENV *env; CONNECTION_PROXY *connection_proxy; std::list<STMT*> stmt_list; std::list<DESC*> desc_list; // Explicit descriptors STMT_OPTIONS stmt_options; MYERROR error; std::shared_ptr<FILE> log_file; // empty shared_ptr char st_error_prefix[255] = { 0 }; std::string database; SQLUINTEGER login_timeout = 0; time_t last_query_time = 0; int txn_isolation = 0; uint port = 0; uint cursor_count = 0; ulong net_buffer_len = 0; uint commit_flag = 0; bool has_query_attrs = false; ulong id; std::recursive_mutex lock; // Whether SQL*ConnectW was used bool unicode = false; // 'ANSI' charset (SQL_C_CHAR) CHARSET_INFO *ansi_charset_info = nullptr, // Connection charset ('ANSI' or utf-8) *cxn_charset_info = nullptr; MY_SYNTAX_MARKERS *syntax = nullptr; // data source used to connect (parsed or stored) DataSource *ds = nullptr; // value of the sql_select_limit currently set for a session // (SQLULEN)(-1) if wasn't set SQLULEN sql_select_limit = -1; // Connection have been put to the pool int need_to_wakeup = 0; bool transaction_open = false; // Flag to indicate whether we have a transaction open fido_callback_func fido_callback = nullptr; telemetry::Telemetry<DBC> telemetry; FAILOVER_HANDLER *fh = nullptr; /* Failover handler */ std::shared_ptr<CONNECTION_HANDLER> connection_handler = nullptr; std::shared_ptr<TOPOLOGY_SERVICE> topology_service = nullptr; DBC(ENV *p_env); void free_explicit_descriptors(); void free_connection_stmts(); void add_desc(DESC* desc); void remove_desc(DESC *desc); SQLRETURN set_error(char *state, const char *message, uint errcode); SQLRETURN set_error(char *state); SQLRETURN connect(DataSource *dsrc, bool failover_enabled, bool is_monitor_connection = false); void execute_prep_stmt(MYSQL_STMT *pstmt, std::string &query, std::vector<MYSQL_BIND> &param_bind, MYSQL_BIND *result_bind); void init_proxy_chain(DataSource *dsrc); std::shared_ptr<TOPOLOGY_SERVICE> get_topology_service() { return this->topology_service ? this->topology_service : std::make_shared<TOPOLOGY_SERVICE>(this->id, ds ? ds->opt_LOG_QUERY : false); } inline bool transactions_supported() { return connection_proxy->get_server_capabilities() & CLIENT_TRANSACTIONS; } inline bool autocommit_is_on() { return connection_proxy->get_server_status() & SERVER_STATUS_AUTOCOMMIT; } void close(); ~DBC(); void set_charset(std::string charset); SQLRETURN set_charset_options(const char* charset); SQLRETURN set_error(myodbc_errid errid, const char* errtext, SQLINTEGER errcode); SQLRETURN execute_query(const char *query, SQLULEN query_length, my_bool req_lock); }; /* Statement states */ enum MY_STATE { ST_UNKNOWN = 0, ST_PREPARED, ST_PRE_EXECUTED, ST_EXECUTED }; enum MY_DUMMY_STATE { ST_DUMMY_UNKNOWN = 0, ST_DUMMY_PREPARED, ST_DUMMY_EXECUTED }; struct MY_LIMIT_CLAUSE { unsigned long long offset; unsigned int row_count; const char *begin, *end; MY_LIMIT_CLAUSE(unsigned long long offs, unsigned int rc, char* b, char *e) : offset(offs), row_count(rc), begin(b), end(e) {} }; struct MY_LIMIT_SCROLLER { tempBuf buf; char *query, *offset_pos; unsigned int row_count; unsigned long long start_offset; unsigned long long next_offset, total_rows, query_len; MY_LIMIT_SCROLLER() : buf(1024), query(buf.buf), offset_pos(query), row_count(0), start_offset(0), next_offset(0), total_rows(0), query_len(0) {} void extend_buf(size_t new_size) { buf.extend_buffer(new_size); } void reset() { next_offset = 0; offset_pos = query; } }; /* Statement primary key handler for cursors */ struct MY_PK_COLUMN { char name[NAME_LEN+1]; my_bool bind_done; MY_PK_COLUMN() { name[0] = 0; bind_done = FALSE; } }; /* Statement cursor handler */ struct MYCURSOR { std::string name; uint pk_count; my_bool pk_validated; MY_PK_COLUMN pkcol[MY_MAX_PK_PARTS]; MYCURSOR() : pk_count(0), pk_validated(FALSE) {} }; enum OUT_PARAM_STATE { OPS_UNKNOWN= 0, OPS_BEING_FETCHED, OPS_PREFETCHED, OPS_STREAMS_PENDING }; #define CAT_SCHEMA_SET_FULL(STMT, C, S, V, CZ, SZ, CL, SL) { \ bool cat_is_set = false; \ if (!STMT->dbc->ds->opt_NO_CATALOG && (CL || !SL)) \ { \ C = V;\ S = nullptr; \ cat_is_set = true; \ } \ if (!STMT->dbc->ds->opt_NO_SCHEMA && !cat_is_set && SZ) \ { \ S = V; \ C = nullptr; \ } \ } #define CAT_SCHEMA_SET(C, S, V) \ CAT_SCHEMA_SET_FULL(stmt, C, S, V, catalog, schema, catalog_len, schema_len) /* Main statement handler */ struct GETDATA{ uint column; /* Which column is being used with SQLGetData() */ char *source; /* Our current position in the source. */ uchar latest[7]; /* Latest character to be converted. */ int latest_bytes; /* Bytes of data in latest. */ int latest_used; /* Bytes of latest that have been used. */ ulong src_offset; /* @todo remove */ ulong dst_bytes; /* Length of data once it is all converted (in chars). */ ulong dst_offset; /* Current offset into dest. (ulong)~0L when not set. */ GETDATA() : column(0), source(NULL), latest_bytes(0), latest_used(0), src_offset(0), dst_bytes(0), dst_offset(0) {} }; struct ODBC_RESULTSET { MYSQL_RES *res = nullptr; ODBC_RESULTSET(MYSQL_RES *r = nullptr) : res(r) {} void reset(MYSQL_RES *r = nullptr) { if (res) mysql_free_result(res); res = r; } // TODO Replace with proxy call MYSQL_RES *release() { MYSQL_RES *tmp = res; res = nullptr; return tmp; } MYSQL_RES * operator=(MYSQL_RES *r) { reset(r); return res; } operator MYSQL_RES*() const { return res; } operator bool() const { return res != nullptr; } ~ODBC_RESULTSET() { reset(); } }; /* A string capable of being a NULL */ struct xstring : public std::string { bool m_is_null = false; using Base = std::string; xstring(std::nullptr_t) : m_is_null(true) {} xstring(char* s) : m_is_null(s == nullptr), Base(s == nullptr ? "" : std::forward<char*>(s)) {} template <class T> xstring(T &&s) : Base(std::forward<T>(s)) {} xstring(SQLULEN v) : Base(std::to_string(v)) {} xstring(long long v) : Base(std::to_string(v)) {} xstring(SQLSMALLINT v) : Base(std::to_string(v)) {} xstring(long int v) : Base(std::to_string(v)) {} xstring(int v) : Base(std::to_string(v)) {} xstring(unsigned int v) : Base(std::to_string(v)) {} const char *c_str() const { return m_is_null ? nullptr : Base::c_str(); } size_t size() const { return m_is_null ? 0 : Base::size(); } bool is_null() { return m_is_null; } }; struct ROW_STORAGE { typedef std::vector<xstring> vstr; typedef std::vector<const char*> pstr; size_t m_rnum = 0, m_cnum = 0, m_cur_row = 0, m_cur_col = 0; bool m_eof = true; /* Data and pointers are in separate containers because for the pointers we will need to get the sequence of pointers returned by vector::data() */ vstr m_data; pstr m_pdata; /* Setting zero for rows or columns makes the storage object invalid */ size_t set_size(size_t rnum, size_t cnum); /* Invalidate the data array. Returns true if the data actually existed and was invalidated. False otherwise. */ bool invalidate() { bool was_invalidated = is_valid(); m_eof = true; set_size(0, 0); return was_invalidated; } /* Returns true if current row was last */ bool eof() { return m_eof; } bool is_valid() { return m_rnum * m_cnum > 0; } bool next_row(); /* Set the row counter to the first row */ void first_row() { m_cur_row = 0; m_eof = m_rnum == 0; } xstring& operator[](size_t idx); void set_data(size_t idx, void *data, size_t size) { if (data) m_data[m_cur_row * m_cnum + idx].assign((const char*)data, size); else m_data[m_cur_row * m_cnum + idx] = nullptr; m_eof = false; } /* Copy row data from bind buffer into the storage one row at a time */ void set_data(MYSQL_BIND *bind) { for(size_t i = 0; i < m_cnum; ++i) { if (!(*bind[i].is_null)) set_data(i, bind[i].buffer, *(bind[i].length)); else set_data(i, nullptr, 0); } } /* Copy data from the storage into the bind buffer one row at a time */ void fill_data(MYSQL_BIND *bind) { if (m_cur_row >= m_rnum || m_eof) return; for(size_t i = 0; i < m_cnum; ++i) { auto &data = m_data[m_cur_row * m_cnum + i]; *(bind[i].is_null) = data.is_null(); *(bind[i].length) = (unsigned long)(data.is_null() ? -1 : data.length()); if (!data.is_null()) { size_t copy_zero = bind[i].buffer_length > *(bind[i].length) ? 1 : 0; memcpy(bind[i].buffer, data.data(), *(bind[i].length) + copy_zero); } } // Set EOF if the last row was filled m_eof = (m_rnum <= (m_cur_row + 1)); // Increment row counter only if not EOF m_cur_row += m_eof ? 0 : 1; } const xstring & operator=(const xstring &val); ROW_STORAGE() { set_size(0, 0); } ROW_STORAGE(size_t rnum, size_t cnum) : m_rnum(rnum), m_cnum(cnum) { set_size(rnum, cnum); } const char **data(); }; enum class EXCEPTION_TYPE {EMPTY_SET, CONN_ERR, GENERAL}; struct ODBCEXCEPTION { EXCEPTION_TYPE m_type = EXCEPTION_TYPE::GENERAL; std::string m_msg; ODBCEXCEPTION(EXCEPTION_TYPE t = EXCEPTION_TYPE::GENERAL) : m_type(t) {} ODBCEXCEPTION(std::string msg, EXCEPTION_TYPE t = EXCEPTION_TYPE::GENERAL) : m_type(t), m_msg(msg) {} }; struct ODBC_STMT { MYSQL_STMT *m_stmt = nullptr; CONNECTION_PROXY *connection_proxy = nullptr; ODBC_STMT(CONNECTION_PROXY *connection_proxy) { if (connection_proxy) m_stmt = connection_proxy->stmt_init();; } operator MYSQL_STMT*() { return m_stmt; } ~ODBC_STMT() { if (m_stmt) connection_proxy->stmt_close(m_stmt); } }; class charPtrBuf { private: std::vector<char*> m_buf; MYSQL_ROW m_external_val = nullptr; public: void reset() { m_buf.clear(); m_external_val = nullptr; } void set_size(size_t size) { m_buf.resize(size); m_external_val = nullptr; } void set(const void* data, size_t size) { set_size(size); char *buf = (char*)data; std::vector<char*> tmpVec(size, buf); m_buf = tmpVec; } operator MYSQL_ROW() const { if (m_external_val || m_buf.size()) return (MYSQL_ROW)(m_external_val ? m_external_val : m_buf.data()); return nullptr; } charPtrBuf &operator=(const MYSQL_ROW external_val) { reset(); m_external_val = external_val; return *this; } }; struct STMT { DBC *dbc; MYSQL_RES *result; my_bool fake_result; charPtrBuf array; charPtrBuf result_array; MYSQL_ROW current_values; MYSQL_ROW (*fix_fields)(STMT *stmt, MYSQL_ROW row); MYSQL_FIELD *fields; MYSQL_ROW_OFFSET end_of_set; tempBuf tempbuf; ROW_STORAGE m_row_storage; MYCURSOR cursor; MYERROR error; STMT_OPTIONS stmt_options; std::string table_name; std::string catalog_name; MY_PARSED_QUERY query, orig_query; std::vector<MYSQL_BIND> param_bind; std::vector<const char*> query_attr_names; std::unique_ptr<my_bool[]> rb_is_null; std::unique_ptr<my_bool[]> rb_err; std::unique_ptr<unsigned long[]> rb_len; std::unique_ptr<unsigned long[]> lengths; my_ulonglong affected_rows; long current_row; long cursor_row; char dae_type; /* data-at-exec type */ GETDATA getdata; uint param_count, current_param, rows_found_in_set; enum MY_STATE state; enum MY_DUMMY_STATE dummy_state; /* APD for data-at-exec on SQLSetPos() */ std::unique_ptr<DESC> setpos_apd; DESC *setpos_apd2; SQLSETPOSIROW setpos_row; SQLUSMALLINT setpos_lock; SQLUSMALLINT setpos_op; MYSQL_STMT *ssps; MYSQL_BIND *result_bind; MY_LIMIT_SCROLLER scroller; enum OUT_PARAM_STATE out_params_state; DESC m_ard, *ard; DESC m_ird, *ird; DESC m_apd, *apd; DESC m_ipd, *ipd; /* implicit descriptors */ DESC *imp_ard; DESC *imp_apd; std::recursive_mutex lock; telemetry::Telemetry<STMT> telemetry; telemetry::Telemetry<DBC>& conn_telemetry() { assert(dbc); return dbc->telemetry; } int ssps_bind_result(); char* extend_buffer(char *to, size_t len); char* extend_buffer(size_t len); char* add_to_buffer(const char *from, size_t len); char* buf() { return tempbuf.buf; } char* endbuf() { return tempbuf.buf + tempbuf.cur_pos; } size_t buf_pos() { return tempbuf.cur_pos; } size_t buf_len() { return tempbuf.buf_len; } size_t field_count(); MYSQL_ROW fetch_row(bool read_unbuffered = false); void buf_set_pos(size_t pos) { tempbuf.cur_pos = pos; } void buf_add_pos(size_t pos) { tempbuf.cur_pos += pos; } void buf_remove_trail_zeroes() { tempbuf.remove_trail_zeroes(); } void alloc_lengths(size_t num); void free_lengths(); void reset_getdata_position(); void reset_setpos_apd(); void allocate_param_bind(uint elements); long compute_cur_row(unsigned fFetchType, SQLLEN irow); SQLRETURN bind_query_attrs(bool use_ssps); void reset(); void free_unbind(); void free_reset_out_params(); void free_reset_params(); void free_fake_result(bool clear_all_results); bool is_dynamic_cursor(); SQLRETURN set_error(myodbc_errid errid, const char *errtext, SQLINTEGER errcode); SQLRETURN set_error(const char *state, const char *errtext, SQLINTEGER errcode); /* Error message and errno is taken from dbc->mysql */ SQLRETURN set_error(myodbc_errid errid); void add_query_attr(const char *name, std::string val); bool query_attr_exists(const char *name); void clear_attr_names() { query_attr_names.clear(); } /* Error message and errno is taken from dbc->mysql */ SQLRETURN set_error(const char *state); STMT(DBC *d) : dbc(d), result(NULL), fake_result(false), array(), result_array(), current_values(NULL), fields(NULL), end_of_set(NULL), tempbuf(), stmt_options(dbc->stmt_options), lengths(nullptr), affected_rows(0), current_row(0), cursor_row(0), dae_type(0), param_count(0), current_param(0), rows_found_in_set(0), state(ST_UNKNOWN), dummy_state(ST_DUMMY_UNKNOWN), setpos_row(0), setpos_lock(0), setpos_op(0), ssps(NULL), result_bind(NULL), out_params_state(OPS_UNKNOWN), m_ard(this, SQL_DESC_ALLOC_AUTO, DESC_APP, DESC_ROW), ard(&m_ard), m_ird(this, SQL_DESC_ALLOC_AUTO, DESC_IMP, DESC_ROW), ird(&m_ird), m_apd(this, SQL_DESC_ALLOC_AUTO, DESC_APP, DESC_PARAM), apd(&m_apd), m_ipd(this, SQL_DESC_ALLOC_AUTO, DESC_IMP, DESC_PARAM), ipd(&m_ipd), imp_ard(ard), imp_apd(apd) { allocate_param_bind(10); LOCK_DBC(dbc); dbc->stmt_list.emplace_back(this); } ~STMT(); void clear_param_bind(); void reset_result_array(); private: /* Create a phony, non-functional STMT handle used as a placeholder. Warning: The hanlde should not be used other than for storing attributes added using `add_query_attr()`. */ STMT(DBC* d, size_t param_cnt) : dbc{d}, query_attr_names{param_cnt}, ssps(nullptr), m_ard(this, SQL_DESC_ALLOC_AUTO, DESC_APP, DESC_ROW), m_ird(this, SQL_DESC_ALLOC_AUTO, DESC_IMP, DESC_ROW), m_apd(this, SQL_DESC_ALLOC_AUTO, DESC_APP, DESC_PARAM), m_ipd(this, SQL_DESC_ALLOC_AUTO, DESC_IMP, DESC_PARAM) {} friend DBC; }; namespace myodbc { struct HENV { SQLHENV henv = nullptr; HENV(unsigned long v) { size_t ver = v; SQLAllocHandle(SQL_HANDLE_ENV, nullptr, &henv); if (SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)ver, 0) != SQL_SUCCESS) { throw MYERROR(SQL_HANDLE_ENV, henv, SQL_ERROR); } } HENV() : HENV(SQL_OV_ODBC3) {} operator SQLHENV() const { return henv; } ~HENV() { SQLFreeHandle(SQL_HANDLE_ENV, henv); } }; struct HDBC { SQLHDBC hdbc = nullptr; SQLHENV henv; xstring connout = nullptr; SQLCHAR ch_out[512] = { 0 }; HDBC(SQLHENV env, DataSource *params) : henv(env) { SQLWSTRING string_connect_in; assert((bool)params->opt_DRIVER); params->opt_DSN.set_default(nullptr); string_connect_in = params->to_kvpair(';'); if (SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc) != SQL_SUCCESS) { throw MYERROR(SQL_HANDLE_ENV, henv, SQL_ERROR); } if (SQLDriverConnectW(hdbc, NULL, (SQLWCHAR*)string_connect_in.c_str(), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT) != SQL_SUCCESS) { throw MYERROR(SQL_HANDLE_DBC, hdbc, SQL_ERROR); } } operator SQLHDBC() const { return hdbc; } ~HDBC() { SQLDisconnect(hdbc); SQLFreeHandle(SQL_HANDLE_DBC, hdbc); } }; struct HSTMT { SQLHDBC hdbc; SQLHSTMT hstmt = nullptr; HSTMT(SQLHDBC dbc) : hdbc(dbc) { if (SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt) != SQL_SUCCESS) { throw MYERROR(SQL_HANDLE_STMT, hstmt, SQL_ERROR); } } operator SQLHSTMT() const { return hstmt; } ~HSTMT() { SQLFreeHandle(SQL_HANDLE_STMT, hstmt); } }; }; extern std::string thousands_sep, decimal_point, default_locale; #ifndef _UNIX_ extern HINSTANCE NEAR s_hModule; /* DLL handle. */ #endif #ifdef WIN32 extern std::string current_dll_location; extern std::string default_plugin_location; #endif /* Resource defines for "SQLDriverConnect" dialog box */ #define ID_LISTBOX 100 #define CONFIGDSN 1001 #define CONFIGDEFAULT 1002 #define EDRIVERCONNECT 1003 /* New data type definitions for compatibility with MySQL 5 */ #ifndef MYSQL_TYPE_NEWDECIMAL # define MYSQL_TYPE_NEWDECIMAL 246 #endif #ifndef MYSQL_TYPE_BIT # define MYSQL_TYPE_BIT 16 #endif MY_LIMIT_CLAUSE find_position4limit(CHARSET_INFO* cs, const char *query, const char * query_end); #include "myutil.h" #include "util/stringutil.h" #include "mylog.h" SQLRETURN SQL_API MySQLColAttribute(SQLHSTMT hstmt, SQLUSMALLINT column, SQLUSMALLINT attrib, SQLCHAR **char_attr, SQLLEN *num_attr); SQLRETURN SQL_API MySQLColumnPrivileges(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *table, SQLSMALLINT table_len, SQLCHAR *column, SQLSMALLINT column_len); SQLRETURN SQL_API MySQLColumns(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *sztable, SQLSMALLINT table_len, SQLCHAR *column, SQLSMALLINT column_len); SQLRETURN SQL_API MySQLConnect(SQLHDBC hdbc, SQLWCHAR *szDSN, SQLSMALLINT cbDSN, SQLWCHAR *szUID, SQLSMALLINT cbUID, SQLWCHAR *szAuth, SQLSMALLINT cbAuth); SQLRETURN SQL_API MySQLDescribeCol(SQLHSTMT hstmt, SQLUSMALLINT column, SQLCHAR **name, SQLSMALLINT *need_free, SQLSMALLINT *type, SQLULEN *def, SQLSMALLINT *scale, SQLSMALLINT *nullable); SQLRETURN SQL_API MySQLDriverConnect(SQLHDBC hdbc, SQLHWND hwnd, SQLWCHAR *in, SQLSMALLINT in_len, SQLWCHAR *out, SQLSMALLINT out_max, SQLSMALLINT *out_len, SQLUSMALLINT completion); SQLRETURN SQL_API MySQLForeignKeys(SQLHSTMT hstmt, SQLCHAR *pkcatalog, SQLSMALLINT pkcatalog_len, SQLCHAR *pkschema, SQLSMALLINT pkschema_len, SQLCHAR *pktable, SQLSMALLINT pktable_len, SQLCHAR *fkcatalog, SQLSMALLINT fkcatalog_len, SQLCHAR *fkschema, SQLSMALLINT fkschema_len, SQLCHAR *fktable, SQLSMALLINT fktable_len); SQLCHAR *MySQLGetCursorName(HSTMT hstmt); SQLRETURN SQL_API MySQLGetInfo(SQLHDBC hdbc, SQLUSMALLINT fInfoType, SQLCHAR **char_info, SQLPOINTER num_info, SQLSMALLINT *value_len); SQLRETURN SQL_API MySQLGetConnectAttr(SQLHDBC hdbc, SQLINTEGER attrib, SQLCHAR **char_attr, SQLPOINTER num_attr); SQLRETURN MySQLGetDescField(SQLHDESC hdesc, SQLSMALLINT recnum, SQLSMALLINT fldid, SQLPOINTER valptr, SQLINTEGER buflen, SQLINTEGER *strlen); SQLRETURN SQL_API MySQLGetDiagField(SQLSMALLINT handle_type, SQLHANDLE handle, SQLSMALLINT record, SQLSMALLINT identifier, SQLCHAR **char_value, SQLPOINTER num_value); SQLRETURN SQL_API MySQLGetDiagRec(SQLSMALLINT handle_type, SQLHANDLE handle, SQLSMALLINT record, SQLCHAR **sqlstate, SQLINTEGER *native, SQLCHAR **message); SQLRETURN SQL_API MySQLGetStmtAttr(SQLHSTMT hstmt, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER BufferLength __attribute__((unused)), SQLINTEGER *StringLengthPtr); SQLRETURN SQL_API MySQLGetTypeInfo(SQLHSTMT hstmt, SQLSMALLINT fSqlType); SQLRETURN SQL_API MySQLPrepare(SQLHSTMT hstmt, SQLCHAR *query, SQLINTEGER len, bool reset_select_limit, bool force_prepare); SQLRETURN SQL_API MySQLPrimaryKeys(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *table, SQLSMALLINT table_len); SQLRETURN SQL_API MySQLProcedureColumns(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *proc, SQLSMALLINT proc_len, SQLCHAR *column, SQLSMALLINT column_len); SQLRETURN SQL_API MySQLProcedures(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *proc, SQLSMALLINT proc_len); SQLRETURN SQL_API MySQLSetConnectAttr(SQLHDBC hdbc, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLengthPtr); SQLRETURN SQL_API MySQLSetCursorName(SQLHSTMT hstmt, SQLCHAR *name, SQLSMALLINT len); SQLRETURN SQL_API MySQLSetStmtAttr(SQLHSTMT hstmt, SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER len); SQLRETURN SQL_API MySQLSpecialColumns(SQLHSTMT hstmt, SQLUSMALLINT type, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *table, SQLSMALLINT table_len, SQLUSMALLINT scope, SQLUSMALLINT nullable); SQLRETURN SQL_API MySQLStatistics(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *table, SQLSMALLINT table_len, SQLUSMALLINT unique, SQLUSMALLINT accuracy); SQLRETURN SQL_API MySQLTablePrivileges(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *table, SQLSMALLINT table_len); SQLRETURN SQL_API MySQLTables(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *table, SQLSMALLINT table_len, SQLCHAR *type, SQLSMALLINT type_len); #endif /* __DRIVER_H__ */