|
|
| version 1.5, 2001/11/16 12:38:52 | version 1.30, 2008/05/04 07:37:31 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser MySQL driver. | Parser MySQL driver. |
| Copyright(c) 2001 ArtLebedev Group(http://www.artlebedev.com) | Copyright(c) 2001, 2003 ArtLebedev Group (http://www.artlebedev.com) |
| Author: Alexander Petrosyan <paf@design.ru>(http://design.ru/paf) | Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru) |
| 2001.07.30 using MySQL 3.23.22b | 2001.07.30 using MySQL 3.23.22b |
| Line 46 static char *lsplit(char **string_ref, c | Line 46 static char *lsplit(char **string_ref, c |
| return result; | return result; |
| } | } |
| static char* rsplit(char* string, char delim) { | |
| if(string) { | |
| char* v=strrchr(string, delim); | |
| if(v) { | |
| *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++); | |
| } | |
| struct Connection { | |
| SQL_Driver_services* services; | |
| MYSQL* handle; | |
| const char* cstrClientCharset; | |
| bool autocommit; | |
| }; | |
| /** | /** |
| MySQL server driver | MySQL server driver |
| */ | */ |
| class MySQL_Driver : public SQL_Driver { | class MySQL_Driver : public SQL_Driver { |
| public: | public: |
| MySQL_Driver() : SQL_Driver() { | MySQL_Driver() : SQL_Driver() {} |
| ~MySQL_Driver() { | |
| if(mysql_server_end) | |
| mysql_server_end(); | |
| } | } |
| /// get api version | /// get api version |
| Line 63 public: | Line 92 public: |
| dlink(dlopen_file_spec):"client library column is empty"; | dlink(dlopen_file_spec):"client library column is empty"; |
| } | } |
| /** connect | /** connect |
| @param used_only_in_connect_url | @param url |
| format: @b user:pass@host[:port]|[/unix/socket]/database? | format: @b user:pass@host[:port]|[/unix/socket]/database? |
| charset=cp1251_koi8& | charset=cp1251_koi8& |
| timeout=3& | timeout=3& |
| compress=1& | compress=1& |
| named_pipe=1 | named_pipe=1 |
| 3.23.22b | 3.23.22b |
| Currently the only option for @b character_set_name is cp1251_koi8. | Currently the only option for @b character_set_name is cp1251_koi8. |
| WARNING: must be used only to connect, for buffer doesn't live long | WARNING: must be used only to connect, for buffer doesn't live long |
| 4.1+ accept not only 'cp1251_koi8' but 'cp1251', 'utf8' and much more | |
| it can be usable for transcoding using sql server | |
| */ | */ |
| void connect( | void connect( |
| char *used_only_in_connect_url, | char *url, |
| SQL_Driver_services& services, | SQL_Driver_services& services, |
| void **connection ///< output: MYSQL * | void **connection_ref ///< output: Connection* |
| ) { | ) { |
| char *user=used_only_in_connect_url; | char *user=url; |
| char *s=lsplit(user, '@'); | char *s=rsplit(user, '@'); |
| char *host=0; | char *host=0; |
| char *unix_socket=0; | char *unix_socket=0; |
| if(s && s[0]=='[') { // unix socket | if(s && s[0]=='[') { // unix socket |
| Line 95 public: | Line 126 public: |
| int port=port_cstr?strtol(port_cstr, &error_pos, 0):0; | int port=port_cstr?strtol(port_cstr, &error_pos, 0):0; |
| char *options=lsplit(db, '?'); | char *options=lsplit(db, '?'); |
| char *charset=0; | char *cstrBackwardCompAskServerToTranscode=0; |
| MYSQL *mysql=mysql_init(NULL); | Connection& connection=*(Connection *)services.malloc(sizeof(Connection)); |
| connection.services=&services; | |
| connection.handle=mysql_init(NULL); | |
| connection.cstrClientCharset=0; | |
| connection.autocommit=true; | |
| *connection_ref=&connection; | |
| while(options) { | while(options) { |
| if(char *key=lsplit(&options, '&')) { | if(char *key=lsplit(&options, '&')) { |
| if(*key) { | if(*key) { |
| if(char *value=lsplit(key, '=')) { | if(char *value=lsplit(key, '=')) { |
| if(strcasecmp(key, "charset")==0) { | if(strcmp(key, "ClientCharset" ) == 0) { |
| charset=value; | toupper_str(value, value, strlen(value)); |
| connection.cstrClientCharset=value; | |
| } else if(strcasecmp(key, "charset")==0) { // left for backward compatibility, consider using ClientCharset | |
| cstrBackwardCompAskServerToTranscode=value; | |
| } else if(strcasecmp(key, "timeout")==0) { | } else if(strcasecmp(key, "timeout")==0) { |
| unsigned int timeout=(unsigned int)atoi(value); | unsigned int timeout=(unsigned int)atoi(value); |
| if(mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&timeout)!=0) | if(mysql_options(connection.handle, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&timeout)!=0) |
| services._throw(mysql_error(mysql)); | services._throw(mysql_error(connection.handle)); |
| } else if(strcasecmp(key, "compress")==0) { | } else if(strcasecmp(key, "compress")==0) { |
| if(atoi(value)) | if(atoi(value)) |
| if(mysql_options(mysql, MYSQL_OPT_COMPRESS, 0)!=0) | if(mysql_options(connection.handle, MYSQL_OPT_COMPRESS, 0)!=0) |
| services._throw(mysql_error(mysql)); | services._throw(mysql_error(connection.handle)); |
| } else if(strcasecmp(key, "named_pipe")==0) { | } else if(strcasecmp(key, "named_pipe")==0) { |
| if(atoi(value)) | if(atoi(value)) |
| if(mysql_options(mysql, MYSQL_OPT_NAMED_PIPE , 0)!=0) | if(mysql_options(connection.handle, MYSQL_OPT_NAMED_PIPE , 0)!=0) |
| services._throw(mysql_error(mysql)); | services._throw(mysql_error(connection.handle)); |
| } else if(strcasecmp(key, "autocommit")==0) { | |
| if(atoi(value)==0) { | |
| connection.autocommit=false; | |
| } | |
| } else | } else |
| services._throw("unknown connect option" /*key*/); | services._throw("unknown connect option" /*key*/); |
| } else | } else |
| Line 125 public: | Line 168 public: |
| } | } |
| } | } |
| if(!mysql_real_connect(mysql, | // if(connection.cstrClientCharset && cstrBackwardCompAskServerToTranscode) |
| host, user, pwd, db, port?port:MYSQL_PORT, unix_socket, 0)) | // services._throw("use 'ClientCharset' option only, " |
| services._throw(mysql_error(mysql)); | // "'charset' option is obsolete and should not be used with new 'ClientCharset' option"); |
| if(!mysql_real_connect(connection.handle, | |
| host, user, pwd, db, port?port:MYSQL_PORT, unix_socket, CLIENT_MULTI_RESULTS)) | |
| services._throw(mysql_error(connection.handle)); | |
| if(charset) { | if(cstrBackwardCompAskServerToTranscode) { |
| // set charset | // set charset |
| char statement[MAX_STRING]="set CHARACTER SET "; // cp1251_koi8 | char statement[MAX_STRING]="set CHARACTER SET "; |
| strncat(statement, charset, MAX_STRING); | strncat(statement, cstrBackwardCompAskServerToTranscode, MAX_STRING); |
| if(mysql_query(mysql, statement)) | exec(connection, statement); |
| services._throw(mysql_error(mysql)); | |
| (*mysql_store_result)(mysql); // throw out the result [don't need but must call] | |
| } | } |
| *(MYSQL **)connection=mysql; | if(!connection.autocommit) |
| exec(connection, "set autocommit=0"); | |
| } | } |
| void disconnect(void *connection) { | |
| mysql_close((MYSQL *)connection); | void exec(Connection& connection, const char* statement) { |
| if(mysql_query(connection.handle, statement)) | |
| connection.services->_throw(mysql_error(connection.handle)); | |
| (*mysql_store_result)(connection.handle); // throw out the result [don't need but must call] | |
| } | } |
| void commit(SQL_Driver_services&, void *) {} | |
| void rollback(SQL_Driver_services&, void *) {} | |
| bool ping(SQL_Driver_services&, void *connection) { | void disconnect(void *aconnection) { |
| return mysql_ping((MYSQL *)connection)==0; | Connection& connection=*static_cast<Connection*>(aconnection); |
| mysql_close(connection.handle); | |
| connection.handle=0; | |
| } | |
| void commit(void *aconnection) { | |
| //_asm int 3; | |
| Connection& connection=*static_cast<Connection*>(aconnection); | |
| if(!connection.autocommit) | |
| exec(connection, "commit"); | |
| } | } |
| void rollback(void *aconnection) { | |
| Connection& connection=*static_cast<Connection*>(aconnection); | |
| unsigned int quote( | if(!connection.autocommit) |
| SQL_Driver_services&, void *connection, | exec(connection, "rollback"); |
| char *to, const char *from, unsigned int length) { | } |
| bool ping(void *aconnection) { | |
| Connection& connection=*static_cast<Connection*>(aconnection); | |
| return mysql_ping(connection.handle)==0; | |
| } | |
| const char* quote(void *aconnection, const char *from, unsigned int length) { | |
| Connection& connection=*static_cast<Connection*>(aconnection); | |
| /* | /* |
| 3.23.22b | 3.23.22b |
| You must allocate the to buffer to be at least length*2+1 bytes long. | You must allocate the to buffer to be at least length*2+1 bytes long. |
| (In the worse case, each character may need to be encoded as using two bytes, | (In the worse case, each character may need to be encoded as using two bytes, |
| and you need room for the terminating null byte.) | and you need room for the terminating null byte.) |
| */ | */ |
| if(to) // store mode | char *result=(char*)connection.services->malloc_atomic(length*2+1); |
| return mysql_escape_string(to, from, length); | mysql_escape_string(result, from, length); |
| else // estimate mode | return result; |
| return length*2+1; | } |
| } | void query(void *aconnection, |
| void query( | const char *astatement, |
| SQL_Driver_services& services, void *connection, | size_t placeholders_count, Placeholder* placeholders, |
| const char *astatement, unsigned long offset, unsigned long limit, | unsigned long offset, unsigned long limit, |
| SQL_Driver_query_event_handlers& handlers) { | SQL_Driver_query_event_handlers& handlers) { |
| Connection& connection=*static_cast<Connection*>(aconnection); | |
| MYSQL *mysql=(MYSQL *)connection; | SQL_Driver_services& services=*connection.services; |
| const char* cstrClientCharset=connection.cstrClientCharset; | |
| MYSQL_RES *res=NULL; | MYSQL_RES *res=NULL; |
| if(placeholders_count>0) | |
| services._throw("bind variables not supported (yet)"); | |
| // transcode from $request:charset to connect-string?client_charset | |
| if(cstrClientCharset) { | |
| size_t transcoded_statement_size; | |
| services.transcode(astatement, strlen(astatement), | |
| astatement, transcoded_statement_size, | |
| services.request_charset(), | |
| cstrClientCharset); | |
| } | |
| const char *statement; | const char *statement; |
| if(offset || limit) { | if(offset || limit) { |
| size_t statement_size=strlen(astatement); | size_t statement_size=strlen(astatement); |
| char *statement_limited=(char *)services.malloc( | char *statement_limited=(char *)services.malloc_atomic( |
| statement_size+MAX_NUMBER*2+8/* limit #,#*/+1); | statement_size+MAX_NUMBER*2+8/* limit #,#*/+1); |
| char *cur=statement_limited; | char *cur=statement_limited; |
| memcpy(cur, astatement, statement_size); cur+=statement_size; | memcpy(cur, astatement, statement_size); cur+=statement_size; |
| Line 189 public: | Line 270 public: |
| } else | } else |
| statement=astatement; | statement=astatement; |
| if(mysql_query(mysql, statement)) | if(mysql_query(connection.handle, statement)) |
| services._throw(mysql_error(mysql)); | services._throw(mysql_error(connection.handle)); |
| if(!(res=mysql_store_result(mysql)) && mysql_field_count(mysql)) | if(!(res=mysql_store_result(connection.handle)) && mysql_field_count(connection.handle)) |
| services._throw(mysql_error(mysql)); | services._throw(mysql_error(connection.handle)); |
| if(!res) // empty result: insert|delete|update|... | if(!res) // empty result: insert|delete|update|... |
| return; | return; |
| int column_count=mysql_num_fields(res); | int column_count=mysql_num_fields(res); |
| if(!column_count) // old client | if(!column_count) // old client |
| column_count=mysql_field_count(mysql); | column_count=mysql_field_count(connection.handle); |
| if(!column_count) { | if(!column_count) { |
| mysql_free_result(res); | mysql_free_result(res); |
| services._throw("result contains no columns"); | services._throw("result contains no columns"); |
| } | } |
| bool failed=false; | |
| SQL_Error sql_error; | |
| #define CHECK(afailed) \ | |
| if(afailed) { \ | |
| failed=true; \ | |
| goto cleanup; \ | |
| } | |
| for(int i=0; i<column_count; i++){ | for(int i=0; i<column_count; i++){ |
| MYSQL_FIELD *field=mysql_fetch_field(res); | if(MYSQL_FIELD *field=mysql_fetch_field(res)) { |
| size_t size=strlen(field->name); | size_t length=strlen(field->name); |
| void *ptr=services.malloc(size); | char* strm=(char*)services.malloc_atomic(length+1); |
| memcpy(ptr, field->name, size); | memcpy(strm, field->name, length+1); |
| handlers.add_column(ptr, size); | const char* str=strm; |
| // transcode to $request:charset from connect-string?client_charset | |
| if(cstrClientCharset) { | |
| services.transcode(str, length, | |
| str, length, | |
| cstrClientCharset, | |
| services.request_charset()); | |
| } | |
| CHECK(handlers.add_column(sql_error, str, length)); | |
| } else { | |
| // seen some broken client, | |
| // which reported "44" for column count of response to "select 2+2" | |
| column_count=i; | |
| break; | |
| } | |
| } | } |
| handlers.before_rows(); | CHECK(handlers.before_rows(sql_error)); |
| while(MYSQL_ROW mysql_row=mysql_fetch_row(res)) { | while(MYSQL_ROW mysql_row=mysql_fetch_row(res)) { |
| handlers.add_row(); | CHECK(handlers.add_row(sql_error)); |
| unsigned long *lengths=mysql_fetch_lengths(res); | unsigned long *lengths=mysql_fetch_lengths(res); |
| for(int i=0; i<column_count; i++){ | for(int i=0; i<column_count; i++){ |
| size_t size=(size_t)lengths[i]; | size_t length=(size_t)lengths[i]; |
| void *ptr; | const char* str; |
| if(size) { | if(length) { |
| ptr=services.malloc(size); | char *strm=(char*)services.malloc_atomic(length+1); |
| memcpy(ptr, mysql_row[i], size); | memcpy(strm, mysql_row[i], length); |
| strm[length]=0; | |
| str=strm; | |
| // transcode to $request:charset from connect-string?client_charset | |
| if(cstrClientCharset) | |
| services.transcode(str, length, | |
| str, length, | |
| cstrClientCharset, | |
| services.request_charset()); | |
| } else | } else |
| ptr=0; | str=0; |
| handlers.add_row_cell(ptr, size); | CHECK(handlers.add_row_cell(sql_error, str, length)); |
| } | } |
| } | } |
| cleanup: | |
| mysql_free_result(res); | mysql_free_result(res); |
| if(failed) | |
| services._throw(sql_error); | |
| } | } |
| private: // mysql client library funcs | private: // mysql client library funcs |
| typedef MYSQL* (STDCALL *t_mysql_init)(MYSQL *); t_mysql_init mysql_init; | 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 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 MYSQL_RES* (STDCALL *t_mysql_store_result)(MYSQL *); t_mysql_store_result mysql_store_result; |
| Line 278 private: // mysql client library funcs | Line 396 private: // mysql client library funcs |
| private: // mysql client library funcs linking | private: // mysql client library funcs linking |
| const char *dlink(const char *dlopen_file_spec) { | const char *dlink(const char *dlopen_file_spec) { |
| if(lt_dlinit()) | |
| return lt_dlerror(); | |
| lt_dlhandle handle=lt_dlopen(dlopen_file_spec); | lt_dlhandle handle=lt_dlopen(dlopen_file_spec); |
| if (!handle) | if (!handle) { |
| if(const char* result=lt_dlerror()) | |
| return result; | |
| return "can not open the dynamic link module"; | return "can not open the dynamic link module"; |
| } | |
| #define GLINK(name) \ | |
| name=(t_##name)lt_dlsym(handle, #name); | |
| #define DSLINK(name, action) \ | #define DSLINK(name, action) \ |
| name=(t_##name)lt_dlsym(handle, #name); \ | GLINK(name) \ |
| if(!name) \ | if(!name) \ |
| action; | action; |
| Line 291 private: // mysql client library funcs l | Line 418 private: // mysql client library funcs l |
| #define SLINK(name) DSLINK(name, name=subst_##name) | #define SLINK(name) DSLINK(name, name=subst_##name) |
| DLINK(mysql_init); | DLINK(mysql_init); |
| GLINK(mysql_server_end); | |
| DLINK(mysql_options); | DLINK(mysql_options); |
| DLINK(mysql_store_result); | DLINK(mysql_store_result); |
| DLINK(mysql_query); | DLINK(mysql_query); |
| Line 311 private: // mysql client library funcs l | Line 439 private: // mysql client library funcs l |
| }; | }; |
| extern "C" SQL_Driver *SQL_DRIVER_CREATE() { | extern "C" SQL_Driver *SQL_DRIVER_CREATE() { |
| return new MySQL_Driver(); | static MySQL_Driver Driver; |
| return &Driver; | |
| } | } |