/** @file Parser MySQL driver. Copyright (c) 2001-2019 Art. Lebedev Studio (http://www.artlebedev.com) Author: Alexandr Petrosian (http://paf.design.ru) 2001-07-30 using MySQL 3.23.22b 2001-11-06 numrows on "HP-UX istok1 B.11.00 A 9000/869 448594332 two-user license" 3.23.42 & 4.0.0.alfa never worked, both subst & .sl version returned 0 */ #include "config_includes.h" #include "pa_sql_driver.h" volatile const char * IDENT_PARSER3MYSQL_C="$Id: parser3mysql.C,v 1.64 2021/11/08 08:21:09 moko Exp $" IDENT_PA_SQL_DRIVER_H; #define NO_CLIENT_LONG_LONG #include "mysql.h" #include "ltdl.h" #define MAX_STRING 0x400 #define MAX_NUMBER 20 #ifdef _MSC_VER # define snprintf _snprintf # define strcasecmp _stricmp #endif static char *lsplit(char *string, char delim){ if(string) { if(char *v=strchr(string, delim)){ *v=0; return v+1; } } return 0; } static char *lsplit(char **string_ref, char delim){ char *result=*string_ref; char *next=lsplit(*string_ref, delim); *string_ref=next; return result; } static char* rsplit(char* string, char delim){ if(string){ if(char* v=strrchr(string, delim)){ *v=0; return v+1; } } return NULL; } static void toupper_str(char *out, const char *in, size_t size){ while(size--) *out++=(char)toupper(*in++); } inline static bool is_column_transcode_required(enum_field_types type) { switch(type) { case MYSQL_TYPE_NULL: #ifdef FIELD_TYPE_NEWDECIMAL case MYSQL_TYPE_NEWDECIMAL: #endif case MYSQL_TYPE_DECIMAL: case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: case MYSQL_TYPE_TINY: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONG: case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_INT24: #ifdef FIELD_TYPE_BIT case MYSQL_TYPE_BIT: #endif case MYSQL_TYPE_DATE: case MYSQL_TYPE_NEWDATE: case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_YEAR: case MYSQL_TYPE_TIMESTAMP: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: return false; break; default: return true; } } inline static char* strdup(SQL_Driver_services& services, char* str, size_t length) { char *strm=(char*)services.malloc_atomic(length+1); memcpy(strm, str, length); strm[length]=0; return strm; } struct Connection { SQL_Driver_services* services; MYSQL* handle; const char* client_charset; bool autocommit; }; /** MySQL server driver */ class MySQL_Driver : public SQL_Driver { public: MySQL_Driver() : SQL_Driver() {} /// get api version int api_version() { return SQL_DRIVER_API_VERSION; } /// initialize driver by loading sql dynamic link library const char *initialize(char *dlopen_file_spec) { return dlopen_file_spec ? dlink(dlopen_file_spec) : "client library column is empty"; } /** connect @param url format: @b user:pass@host[:port]|[/unix/socket]/database? charset=value& // transcode by server with command 'SET NAMES value' ClientCharset=charset& // transcode by parser timeout=3& compress=0& named_pipe=1& autocommit=1& multi_statements=0 // allows more then one statement in one query 4.1+ accept not 'cp1251_koi8' but 'cp1251', 'utf8' and much more it is usable for transcoding using sql server */ void connect(char *url, SQL_Driver_services& services, void **connection_ref /*< output: Connection* */){ char *user=url; char *s=rsplit(user, '@'); char *host=0; char *unix_socket=0; if(s && s[0]=='[') { // unix socket unix_socket=1+s; s=lsplit(unix_socket, ']'); } else { // IP host=s; } char *db=lsplit(s, '/'); char *pwd=lsplit(user, ':'); char *error_pos=0; char *options=lsplit(db, '?'); char *charset=0; int client_flag=CLIENT_MULTI_RESULTS; Connection& connection=*(Connection *)services.malloc(sizeof(Connection)); *connection_ref=&connection; connection.services=&services; connection.handle=mysql_init(NULL); connection.client_charset=0; connection.autocommit=true; while(1){ char *next_host=lsplit(host, ','); char *host_options=next_host && options ? strdup(services, options, strlen(options)) : options; char *port_cstr=lsplit(host, ':'); int port=port_cstr?strtol(port_cstr, &error_pos, 0):0; while(host_options){ char *key=lsplit(&host_options, '&'); if(key && *key){ if(char *value=lsplit(key, '=')){ if(strcmp(key, "ClientCharset")==0){ // transcoding with parser toupper_str(value, value, strlen(value)); connection.client_charset=value; } else if(strcasecmp(key, "charset")==0){ // transcoding with server charset=value; } else if(strcasecmp(key, "timeout")==0){ unsigned int timeout=(unsigned int)atoi(value); if(mysql_options(connection.handle, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&timeout)!=0) services._throw(mysql_error(connection.handle)); } else if(strcasecmp(key, "compress")==0){ if(atoi(value)) if(mysql_options(connection.handle, MYSQL_OPT_COMPRESS, 0)!=0) services._throw(mysql_error(connection.handle)); } else if(strcasecmp(key, "named_pipe")==0){ if(atoi(value)) if(mysql_options(connection.handle, MYSQL_OPT_NAMED_PIPE, 0)!=0) services._throw(mysql_error(connection.handle)); } else if(strcasecmp(key, "local_infile")==0){ if(atoi(value)) if(mysql_options(connection.handle, MYSQL_OPT_LOCAL_INFILE, 0)!=0) services._throw(mysql_error(connection.handle)); } else if(strcasecmp(key, "autocommit")==0){ if(atoi(value)==0) connection.autocommit=false; } else if(strcasecmp(key, "multi_statements")==0){ if(atoi(value)!=0) client_flag=CLIENT_MULTI_STATEMENTS; } else if(strcasecmp(key, "config_file")==0){ if(mysql_options(connection.handle, MYSQL_READ_DEFAULT_FILE, value)!=0) services._throw(mysql_error(connection.handle)); } else if(strcasecmp(key, "config_group")==0){ if(mysql_options(connection.handle, MYSQL_READ_DEFAULT_GROUP, value)!=0) services._throw(mysql_error(connection.handle)); } else services._throw("unknown connect option" /*key*/); } else services._throw("connect option without =value" /*key*/); } } if(mysql_real_connect(connection.handle, host, user, pwd, db, port, unix_socket, client_flag)) break; if(!next_host) services._throw(mysql_error(connection.handle)); host=next_host; } if(charset){ char statement[MAX_STRING]; snprintf(statement, MAX_STRING, "SET NAMES %s", charset); _exec(connection, statement); } if(!connection.autocommit) _exec(connection, "SET AUTOCOMMIT=0"); } void disconnect(void *aconnection) { Connection& connection=*static_cast(aconnection); mysql_close(connection.handle); connection.handle=0; } void commit(void *aconnection) { Connection& connection=*static_cast(aconnection); if(!connection.autocommit) _exec(connection, "COMMIT"); } void rollback(void *aconnection) { Connection& connection=*static_cast(aconnection); if(!connection.autocommit) _exec(connection, "ROLLBACK"); } bool ping(void *aconnection) { Connection& connection=*static_cast(aconnection); return mysql_ping(connection.handle)==0; } // charset here is services.request_charset(), not connection.client_charset // thus we can't use the sql server quoting support const char* quote(void *aconnection, const char *str, unsigned int length) { const char* from; const char* from_end=str+length; size_t quoted=0; for(from=str; from(aconnection); char *result=(char*)connection.services->malloc_atomic(length + quoted + 1); char *to = result; for(from=str; from(aconnection); SQL_Driver_services& services=*connection.services; MYSQL_RES *res=NULL; if(placeholders_count>0) services._throw("bind variables not supported yet"); bool transcode_needed=_transcode_required(connection); size_t statement_size=0; if(transcode_needed) { statement_size=strlen(astatement); // transcode query from $request:charset to ?ClientCharset services.transcode(astatement, statement_size, astatement, statement_size, services.request_charset(), connection.client_charset); } const char *statement; if(offset || limit!=SQL_NO_LIMIT) { if(!statement_size) statement_size=strlen(astatement); char *statement_limited=(char *)services.malloc_atomic( statement_size+MAX_NUMBER*2+8/* LIMIT #,#*/+1); char *cur=statement_limited; memcpy(cur, astatement, statement_size); cur+=statement_size; cur+=sprintf(cur, " LIMIT "); if(offset) cur+=snprintf(cur, MAX_NUMBER, "%lu,", offset); if(limit!=SQL_NO_LIMIT) cur+=snprintf(cur, MAX_NUMBER, "%lu", limit); statement=statement_limited; } else statement=astatement; if(mysql_query(connection.handle, statement)) _throw(connection, mysql_error(connection.handle)); int next_result; do { if(res=mysql_store_result(connection.handle)){ size_t column_count=mysql_num_fields(res); if(!column_count) // old client column_count=mysql_field_count(connection.handle); if(!column_count){ mysql_free_result(res); services._throw("result contains no columns"); } SQL_Error sql_error; #define CHECK(afailed) \ if(afailed) { \ mysql_free_result(res); \ services._throw(sql_error); \ } #define DO_FETCH_FIELDS(transcode_column_name) \ for(size_t i=0; iname_length; \ const char* str=strdup(services, field->name, length); \ transcode_column_name \ CHECK(handlers.add_column(sql_error, str, length)); \ } else { \ /* seen broken client, that reported "44" column count for "select 2+2" */ \ column_count=i; \ break; \ } \ } #define DO_FETCH_ROWS(transcode_cell_value) \ while(MYSQL_ROW mysql_row=mysql_fetch_row(res)) { \ CHECK(handlers.add_row(sql_error)); \ unsigned long *lengths=mysql_fetch_lengths(res); \ for(size_t i=0; itype); // transcode column's name from ?ClientCharset to $request:charset services.transcode(str, length, str, length, connection.client_charset, services.request_charset()); ) CHECK(handlers.before_rows(sql_error)); DO_FETCH_ROWS( // transcode cell's value from ?ClientCharset to $request:charset if(transcode_column[i]) services.transcode(str, length, str, length, connection.client_charset, services.request_charset()); ) #ifdef _MSC_VER services.realloc(transcode_column,0); #endif } else { // without transcoding DO_FETCH_FIELDS({}) CHECK(handlers.before_rows(sql_error)); DO_FETCH_ROWS({}) } mysql_free_result(res); } else { if(mysql_field_count(connection.handle)) _throw(connection, mysql_error(connection.handle)); // empty result: insert|delete|update|... } next_result = mysql_next_result(connection.handle); if (next_result > 0) _throw(connection, mysql_error(connection.handle)); } while (next_result == 0); } private: void _exec(Connection& connection, const char* statement) { if(mysql_query(connection.handle, statement)) _throw(connection, mysql_error(connection.handle)); (*mysql_store_result)(connection.handle); // throw out the result [don't need but must call] } void _throw(Connection& connection, const char* aerr_msg) { size_t length=strlen(aerr_msg); if(length && _transcode_required(connection)) { connection.services->transcode(aerr_msg, length, aerr_msg, length, connection.client_charset, connection.services->request_charset()); } connection.services->_throw(aerr_msg); } bool _transcode_required(Connection& connection){ return (connection.client_charset && strcmp(connection.client_charset, connection.services->request_charset())!=0); } private: // mysql client library funcs typedef MYSQL* (STDCALL *t_mysql_init)(MYSQL *); t_mysql_init mysql_init; typedef void (STDCALL *t_mysql_server_end)(); t_mysql_server_end mysql_server_end; typedef int (STDCALL *t_mysql_options)(MYSQL *mysql, enum mysql_option option, const char *arg); t_mysql_options mysql_options; typedef MYSQL_RES* (STDCALL *t_mysql_store_result)(MYSQL *); t_mysql_store_result mysql_store_result; typedef int (STDCALL *t_mysql_query)(MYSQL *, const char *q); t_mysql_query mysql_query; typedef char* (STDCALL *t_mysql_error)(MYSQL *); t_mysql_error mysql_error; static char* STDCALL subst_mysql_error(MYSQL *mysql) { return (mysql)->net.last_error; } typedef MYSQL* (STDCALL *t_mysql_real_connect)(MYSQL *, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned int clientflag); t_mysql_real_connect mysql_real_connect; typedef void (STDCALL *t_mysql_close)(MYSQL *); t_mysql_close mysql_close; typedef int (STDCALL *t_mysql_ping)(MYSQL *); t_mysql_ping mysql_ping; typedef unsigned long (STDCALL *t_mysql_escape_string)(char *to,const char *from, unsigned long from_length); t_mysql_escape_string mysql_escape_string; typedef void (STDCALL *t_mysql_free_result)(MYSQL_RES *result); t_mysql_free_result mysql_free_result; typedef unsigned long* (STDCALL *t_mysql_fetch_lengths)(MYSQL_RES *result); t_mysql_fetch_lengths mysql_fetch_lengths; typedef MYSQL_ROW (STDCALL *t_mysql_fetch_row)(MYSQL_RES *result); t_mysql_fetch_row mysql_fetch_row; typedef MYSQL_FIELD* (STDCALL *t_mysql_fetch_field)(MYSQL_RES *result); t_mysql_fetch_field mysql_fetch_field; typedef unsigned int (STDCALL *t_mysql_num_fields)(MYSQL_RES *); t_mysql_num_fields mysql_num_fields; typedef unsigned int (STDCALL *t_mysql_field_count)(MYSQL *); t_mysql_field_count mysql_field_count; typedef unsigned int (STDCALL *t_mysql_next_result)(MYSQL *); t_mysql_next_result mysql_next_result; static unsigned int STDCALL subst_mysql_num_fields(MYSQL_RES *res) { return res->field_count; } static unsigned int STDCALL subst_mysql_field_count(MYSQL *mysql) { return mysql->field_count; } static unsigned int STDCALL subst_mysql_next_result(MYSQL *mysql) { return -1; } private: // mysql client library funcs linking const char *dlink(char *dlopen_file_spec) { if(lt_dlinit()){ if(const char* result=lt_dlerror()) return result; return "can not prepare to dynamic loading"; } lt_dlhandle handle; do { char *next=lsplit(dlopen_file_spec, ','); handle=lt_dlopen(dlopen_file_spec); dlopen_file_spec=next; } while (!handle && dlopen_file_spec); if(!handle){ if(const char* result=lt_dlerror()) return result; return "can not open the dynamic link module"; } #define GLINK(name) \ name=(t_##name)lt_dlsym(handle, #name); #define DSLINK(name, action) \ GLINK(name) \ if(!name) \ action; #define DLINK(name) DSLINK(name, return "function " #name " was not found") #define SLINK(name) DSLINK(name, name=subst_##name) DLINK(mysql_init); GLINK(mysql_server_end); DLINK(mysql_options); DLINK(mysql_store_result); DLINK(mysql_query); SLINK(mysql_error); DLINK(mysql_real_connect); DLINK(mysql_close); DLINK(mysql_ping); DLINK(mysql_escape_string); DLINK(mysql_free_result); DLINK(mysql_fetch_lengths); DLINK(mysql_fetch_row); DLINK(mysql_fetch_field); SLINK(mysql_num_fields); SLINK(mysql_field_count); SLINK(mysql_next_result); return 0; } }; extern "C" SQL_Driver *SQL_DRIVER_CREATE() { static MySQL_Driver Driver; return &Driver; }