Annotation of sql/mysql/parser3mysql.C, revision 1.37
1.1 parser 1: /** @file
2: Parser MySQL driver.
3:
1.36 misha 4: Copyright(c) 2001-2009 ArtLebedev Group (http://www.artlebedev.com)
1.1 parser 5:
1.7 paf 6: Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
1.1 parser 7:
1.36 misha 8: 2001-07-30 using MySQL 3.23.22b
1.3 paf 9:
1.36 misha 10: 2001-11-06 numrows on "HP-UX istok1 B.11.00 A 9000/869 448594332 two-user license"
1.3 paf 11: 3.23.42 & 4.0.0.alfa never worked, both subst & .sl version returned 0
1.1 parser 12: */
1.37 ! moko 13: static const char *RCSId="$Id: parser3mysql.C,v 1.36 2009-04-10 12:17:02 misha Exp $";
1.1 parser 14:
15: #include "config_includes.h"
16:
17: #include "pa_sql_driver.h"
18:
19: #define NO_CLIENT_LONG_LONG
20: #include "mysql.h"
21: #include "ltdl.h"
22:
23: #define MAX_STRING 0x400
24: #define MAX_NUMBER 20
25:
26: #if _MSC_VER
27: # define snprintf _snprintf
1.2 parser 28: # define strcasecmp _stricmp
1.1 parser 29: #endif
30:
1.35 misha 31: // for mysql < 4.1
1.36 misha 32: #if !defined(CLIENT_MULTI_RESULTS) || !defined(CLIENT_MULTI_STATEMENTS)
33: # define OLD_MYSQL_CLIENT 1
34: #endif
35:
1.35 misha 36: #ifndef CLIENT_MULTI_RESULTS
1.36 misha 37: # define CLIENT_MULTI_RESULTS (1UL << 17)
1.35 misha 38: #endif
1.36 misha 39:
1.35 misha 40: #ifndef CLIENT_MULTI_STATEMENTS
1.36 misha 41: # define CLIENT_MULTI_STATEMENTS (1UL << 16)
1.35 misha 42: #endif
43:
1.34 misha 44: static char *lsplit(char *string, char delim){
1.32 misha 45: if(string) {
1.34 misha 46: if(char *v=strchr(string, delim)){
1.1 parser 47: *v=0;
48: return v+1;
49: }
1.32 misha 50: }
51: return 0;
1.1 parser 52: }
53:
1.34 misha 54: static char *lsplit(char **string_ref, char delim){
1.32 misha 55: char *result=*string_ref;
1.2 parser 56: char *next=lsplit(*string_ref, delim);
1.32 misha 57: *string_ref=next;
58: return result;
1.2 parser 59: }
60:
1.34 misha 61: static char* rsplit(char* string, char delim){
62: if(string){
63: if(char* v=strrchr(string, delim)){
1.26 paf 64: *v=0;
65: return v+1;
66: }
1.32 misha 67: }
68: return NULL;
1.26 paf 69: }
70:
1.34 misha 71: static void toupper_str(char *out, const char *in, size_t size){
1.19 paf 72: while(size--)
73: *out++=(char)toupper(*in++);
74: }
75:
1.14 paf 76: struct Connection {
1.16 paf 77: SQL_Driver_services* services;
1.15 paf 78:
1.14 paf 79: MYSQL* handle;
1.33 misha 80: const char* client_charset;
1.14 paf 81: bool autocommit;
82: };
83:
1.29 misha 84:
1.1 parser 85: /**
86: MySQL server driver
87: */
88: class MySQL_Driver : public SQL_Driver {
89: public:
90:
1.29 misha 91: MySQL_Driver() : SQL_Driver() {}
92:
1.35 misha 93: #ifndef FREEBSD4
1.29 misha 94: ~MySQL_Driver() {
95: if(mysql_server_end)
96: mysql_server_end();
1.1 parser 97: }
1.35 misha 98: #endif
1.1 parser 99:
100: /// get api version
101: int api_version() { return SQL_DRIVER_API_VERSION; }
1.33 misha 102:
1.1 parser 103: /// initialize driver by loading sql dynamic link library
1.4 paf 104: const char *initialize(char *dlopen_file_spec) {
1.1 parser 105: return dlopen_file_spec?
106: dlink(dlopen_file_spec):"client library column is empty";
107: }
1.33 misha 108:
1.1 parser 109: /** connect
1.23 paf 110: @param url
1.2 parser 111: format: @b user:pass@host[:port]|[/unix/socket]/database?
1.33 misha 112: charset=value& // transcode by server with command 'SET CHARACTER SET value'
1.32 misha 113: ClientCharset=charset& // transcode by parser
1.2 parser 114: timeout=3&
1.32 misha 115: compress=0&
116: named_pipe=1&
1.33 misha 117: autocommit=1&
1.35 misha 118: multi_statements=0 // allows more then one statement in one query
119: old_client=1 // simulates 3.xx client. not compatible with multi_statements option
1.33 misha 120: 3.x, 4.0
121: only option for charset is cp1251_koi8.
122: 4.1+ accept not 'cp1251_koi8' but 'cp1251', 'utf8' and much more
123: it is usable for transcoding using sql server
1.1 parser 124: */
125: void connect(
1.33 misha 126: char *url,
127: SQL_Driver_services& services,
128: void **connection_ref ///< output: Connection*
129: ){
1.23 paf 130: char *user=url;
1.26 paf 131: char *s=rsplit(user, '@');
1.1 parser 132: char *host=0;
133: char *unix_socket=0;
134: if(s && s[0]=='[') { // unix socket
135: unix_socket=1+s;
136: s=lsplit(unix_socket, ']');
137: } else { // IP
138: host=s;
139: }
140: char *db=lsplit(s, '/');
141: char *pwd=lsplit(user, ':');
142: char *error_pos=0;
143: char *port_cstr=lsplit(host, ':');
144: int port=port_cstr?strtol(port_cstr, &error_pos, 0):0;
1.2 parser 145: char *options=lsplit(db, '?');
146:
1.33 misha 147: char *charset=0;
1.35 misha 148:
149: #ifdef OLD_MYSQL_CLIENT
150: int client_flag=0;
151: #else
1.34 misha 152: int client_flag=CLIENT_MULTI_RESULTS;
1.35 misha 153: #endif
1.1 parser 154:
1.33 misha 155: Connection& connection=*(Connection *)services.malloc(sizeof(Connection));
156: *connection_ref=&connection;
1.15 paf 157: connection.services=&services;
1.32 misha 158: connection.handle=mysql_init(NULL);
1.33 misha 159: connection.client_charset=0;
1.14 paf 160: connection.autocommit=true;
1.2 parser 161:
1.32 misha 162: while(options){
163: if(char *key=lsplit(&options, '&')){
164: if(*key){
165: if(char *value=lsplit(key, '=')){
1.33 misha 166: if(strcmp(key, "ClientCharset")==0){ // transcoding with parser
1.21 paf 167: toupper_str(value, value, strlen(value));
1.33 misha 168: connection.client_charset=value;
1.32 misha 169: } else if(strcasecmp(key, "charset")==0){ // transcoding with server
1.33 misha 170: charset=value;
1.32 misha 171: } else if(strcasecmp(key, "timeout")==0){
1.2 parser 172: unsigned int timeout=(unsigned int)atoi(value);
1.14 paf 173: if(mysql_options(connection.handle, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&timeout)!=0)
174: services._throw(mysql_error(connection.handle));
1.32 misha 175: } else if(strcasecmp(key, "compress")==0){
1.2 parser 176: if(atoi(value))
1.14 paf 177: if(mysql_options(connection.handle, MYSQL_OPT_COMPRESS, 0)!=0)
178: services._throw(mysql_error(connection.handle));
1.32 misha 179: } else if(strcasecmp(key, "named_pipe")==0){
1.2 parser 180: if(atoi(value))
1.33 misha 181: if(mysql_options(connection.handle, MYSQL_OPT_NAMED_PIPE, 0)!=0)
1.14 paf 182: services._throw(mysql_error(connection.handle));
1.32 misha 183: } else if(strcasecmp(key, "autocommit")==0){
1.31 misha 184: if(atoi(value)==0)
1.14 paf 185: connection.autocommit=false;
1.34 misha 186: } else if(strcasecmp(key, "multi_statements")==0){
1.35 misha 187: #ifdef OLD_MYSQL_CLIENT
188: services._throw("driver was built with old mysql includes so multi_statements option can't be used");
189: #else
1.34 misha 190: if(!(client_flag==CLIENT_MULTI_RESULTS || client_flag==CLIENT_MULTI_STATEMENTS))
191: services._throw("you can't specify together options old_client and multi_statements");
192: if(atoi(value)!=0)
193: client_flag=CLIENT_MULTI_STATEMENTS;
1.35 misha 194: #endif
1.34 misha 195: } else if(strcasecmp(key, "old_client")==0){
1.35 misha 196: #ifdef OLD_MYSQL_CLIENT
197: services._throw("driver was built with old mysql includes so old_client option can't be used");
198: #else
1.34 misha 199: if(!(client_flag==CLIENT_MULTI_RESULTS || client_flag==0))
200: services._throw("you can't specify together options old_client and multi_statements");
201: if(atoi(value)!=0)
202: client_flag=0;
1.35 misha 203: #endif
1.2 parser 204: } else
205: services._throw("unknown connect option" /*key*/);
206: } else
207: services._throw("connect option without =value" /*key*/);
208: }
209: }
210: }
211:
1.33 misha 212: if(!mysql_real_connect(
213: connection.handle,
214: host, user, pwd, db,
215: port?port:MYSQL_PORT,
216: unix_socket,
1.34 misha 217: client_flag
1.33 misha 218: )
219: ){
1.14 paf 220: services._throw(mysql_error(connection.handle));
1.33 misha 221: }
1.1 parser 222:
1.33 misha 223: if(charset){
1.32 misha 224: char statement[MAX_STRING]="SET CHARACTER SET ";
1.33 misha 225: strncat(statement, charset, MAX_STRING);
226: _exec(connection, statement);
1.1 parser 227: }
228:
1.14 paf 229: if(!connection.autocommit)
1.33 misha 230: _exec(connection, "SET AUTOCOMMIT=0");
1.1 parser 231: }
1.14 paf 232:
233: void disconnect(void *aconnection) {
234: Connection& connection=*static_cast<Connection*>(aconnection);
235: mysql_close(connection.handle);
1.17 paf 236: connection.handle=0;
1.1 parser 237: }
1.32 misha 238:
1.15 paf 239: void commit(void *aconnection) {
1.14 paf 240: Connection& connection=*static_cast<Connection*>(aconnection);
241: if(!connection.autocommit)
1.33 misha 242: _exec(connection, "COMMIT");
1.14 paf 243: }
1.32 misha 244:
1.15 paf 245: void rollback(void *aconnection) {
1.14 paf 246: Connection& connection=*static_cast<Connection*>(aconnection);
247: if(!connection.autocommit)
1.33 misha 248: _exec(connection, "ROLLBACK");
1.14 paf 249: }
250:
1.15 paf 251: bool ping(void *aconnection) {
1.14 paf 252: Connection& connection=*static_cast<Connection*>(aconnection);
253: return mysql_ping(connection.handle)==0;
1.1 parser 254: }
255:
1.37 ! moko 256: // charset here is services.request_charset(), not connection.client_charset
! 257: // thus we can't use the sql server quoting support
! 258: const char* quote(void *aconnection, const char *str, unsigned int length) {
! 259: const char* from;
! 260: const char* from_end=str+length;
! 261:
! 262: size_t quoted=0;
! 263:
! 264: for(from=str; from<from_end; from++){
! 265: switch (*from) {
! 266: case 0:
! 267: case '\n':
! 268: case '\r':
! 269: case '\032':
! 270: case '\\':
! 271: case '\'':
! 272: case '"':
! 273: quoted++;
! 274: }
! 275: }
! 276:
! 277: if(!quoted)
! 278: return str;
! 279:
1.15 paf 280: Connection& connection=*static_cast<Connection*>(aconnection);
1.37 ! moko 281: char *result=(char*)connection.services->malloc_atomic(length + quoted + 1);
! 282: char *to = result;
! 283:
! 284: for(from=str; from<from_end; from++){
! 285: char escape;
! 286: switch (*from) {
! 287: case 0:
! 288: escape= '0';
! 289: break;
! 290: case '\n':
! 291: escape= 'n';
! 292: break;
! 293: case '\r':
! 294: escape= 'r';
! 295: break;
! 296: case '\032':
! 297: escape= 'Z';
! 298: break;
! 299: case '\\':
! 300: case '\'':
! 301: case '"':
! 302: escape= *from;
! 303: break;
! 304: default:
! 305: *to++=*from;
! 306: continue;
! 307: }
! 308: *to++= '\\';
! 309: *to++= escape;
! 310: }
! 311:
! 312: *to=0;
1.13 paf 313: return result;
1.1 parser 314: }
1.33 misha 315:
1.24 paf 316: void query(void *aconnection,
1.33 misha 317: const char *astatement,
318: size_t placeholders_count, Placeholder* placeholders,
319: unsigned long offset, unsigned long limit,
320: SQL_Driver_query_event_handlers& handlers
321: ){
1.14 paf 322: Connection& connection=*static_cast<Connection*>(aconnection);
1.15 paf 323: SQL_Driver_services& services=*connection.services;
1.1 parser 324: MYSQL_RES *res=NULL;
1.24 paf 325:
326: if(placeholders_count>0)
327: services._throw("bind variables not supported (yet)");
1.1 parser 328:
1.33 misha 329: bool transcode_needed=_transcode_required(connection);
330:
331: // transcode query from $request:charset to ?ClientCharset
332: if(transcode_needed){
333: size_t length=strlen(astatement);
334: services.transcode(astatement, length,
335: astatement, length,
1.19 paf 336: services.request_charset(),
1.33 misha 337: connection.client_charset);
1.19 paf 338: }
339:
1.1 parser 340: const char *statement;
1.33 misha 341: if(offset || limit!=SQL_NO_LIMIT){
1.1 parser 342: size_t statement_size=strlen(astatement);
1.13 paf 343: char *statement_limited=(char *)services.malloc_atomic(
1.33 misha 344: statement_size+MAX_NUMBER*2+8/* LIMIT #,#*/+1);
1.1 parser 345: char *cur=statement_limited;
346: memcpy(cur, astatement, statement_size); cur+=statement_size;
1.33 misha 347: cur+=sprintf(cur, " LIMIT ");
1.1 parser 348: if(offset)
349: cur+=snprintf(cur, MAX_NUMBER+1, "%u,", offset);
1.33 misha 350: if(limit!=SQL_NO_LIMIT)
1.1 parser 351: cur+=snprintf(cur, MAX_NUMBER, "%u", limit);
352: statement=statement_limited;
353: } else
354: statement=astatement;
355:
1.14 paf 356: if(mysql_query(connection.handle, statement))
1.33 misha 357: _throw(connection, mysql_error(connection.handle));
1.14 paf 358: if(!(res=mysql_store_result(connection.handle)) && mysql_field_count(connection.handle))
1.33 misha 359: _throw(connection, mysql_error(connection.handle));
1.1 parser 360: if(!res) // empty result: insert|delete|update|...
361: return;
362:
363: int column_count=mysql_num_fields(res);
364: if(!column_count) // old client
1.14 paf 365: column_count=mysql_field_count(connection.handle);
1.1 parser 366:
1.33 misha 367: if(!column_count){
1.1 parser 368: mysql_free_result(res);
369: services._throw("result contains no columns");
370: }
1.8 paf 371:
1.9 paf 372: bool failed=false;
373: SQL_Error sql_error;
374: #define CHECK(afailed) \
375: if(afailed) { \
376: failed=true; \
377: goto cleanup; \
378: }
379:
380: for(int i=0; i<column_count; i++){
1.33 misha 381: if(MYSQL_FIELD *field=mysql_fetch_field(res)){
1.20 paf 382: size_t length=strlen(field->name);
383: char* strm=(char*)services.malloc_atomic(length+1);
384: memcpy(strm, field->name, length+1);
385: const char* str=strm;
1.19 paf 386:
1.33 misha 387: // transcode column name from ?ClientCharset to $request:charset
388: if(transcode_needed){
1.19 paf 389: services.transcode(str, length,
390: str, length,
1.33 misha 391: connection.client_charset,
1.19 paf 392: services.request_charset());
393: }
394:
395: CHECK(handlers.add_column(sql_error, str, length));
1.12 paf 396: } else {
1.32 misha 397: // seen some broken client,
398: // which reported "44" for column count of response to "select 2+2"
399: column_count=i;
400: break;
1.12 paf 401: }
1.9 paf 402: }
1.1 parser 403:
1.9 paf 404: CHECK(handlers.before_rows(sql_error));
405:
1.33 misha 406: while(MYSQL_ROW mysql_row=mysql_fetch_row(res)){
407: CHECK(handlers.add_row(sql_error));
408: unsigned long *lengths=mysql_fetch_lengths(res);
409: for(int i=0; i<column_count; i++){
410: size_t length=(size_t)lengths[i];
1.20 paf 411: const char* str;
1.33 misha 412: if(length){
413: char *strm=(char*)services.malloc_atomic(length+1);
414: memcpy(strm, mysql_row[i], length);
1.20 paf 415: strm[length]=0;
416: str=strm;
1.19 paf 417:
1.33 misha 418: // transcode cell value from ?ClientCharset to $request:charset
419: if(transcode_needed)
1.19 paf 420: services.transcode(str, length,
421: str, length,
1.33 misha 422: connection.client_charset,
1.19 paf 423: services.request_charset());
1.32 misha 424: } else
425: str=0;
426: CHECK(handlers.add_row_cell(sql_error, str, length));
427: }
428: }
1.9 paf 429: cleanup:
1.1 parser 430: mysql_free_result(res);
1.9 paf 431: if(failed)
432: services._throw(sql_error);
1.1 parser 433: }
434:
1.33 misha 435: private:
436: void _exec(Connection& connection, const char* statement) {
437: if(mysql_query(connection.handle, statement))
438: _throw(connection, mysql_error(connection.handle));
439: (*mysql_store_result)(connection.handle); // throw out the result [don't need but must call]
440: }
441:
442: void _throw(Connection& connection, const char* aerr_msg) {
443: size_t length=strlen(aerr_msg);
444: if(length && _transcode_required(connection)) {
445: connection.services->transcode(aerr_msg, length,
446: aerr_msg, length,
447: connection.client_charset,
448: connection.services->request_charset());
449: }
450: connection.services->_throw(aerr_msg);
451: }
452:
453: bool _transcode_required(Connection& connection){
454: return (connection.client_charset && strcmp(connection.client_charset, connection.services->request_charset())!=0);
455: }
456:
1.1 parser 457: private: // mysql client library funcs
458:
1.33 misha 459: typedef MYSQL* (STDCALL *t_mysql_init)(MYSQL *); t_mysql_init mysql_init;
1.1 parser 460:
1.33 misha 461: typedef void (STDCALL *t_mysql_server_end)(); t_mysql_server_end mysql_server_end;
1.29 misha 462:
1.33 misha 463: typedef int (STDCALL *t_mysql_options)(MYSQL *mysql, enum mysql_option option, const char *arg); t_mysql_options mysql_options;
1.2 parser 464:
1.1 parser 465: typedef MYSQL_RES* (STDCALL *t_mysql_store_result)(MYSQL *); t_mysql_store_result mysql_store_result;
466:
467: typedef int (STDCALL *t_mysql_query)(MYSQL *, const char *q); t_mysql_query mysql_query;
468:
1.33 misha 469: typedef char* (STDCALL *t_mysql_error)(MYSQL *); t_mysql_error mysql_error;
1.1 parser 470: static char* STDCALL subst_mysql_error(MYSQL *mysql) { return (mysql)->net.last_error; }
471:
1.33 misha 472: typedef MYSQL* (STDCALL *t_mysql_real_connect)(MYSQL *, const char *host,
473: const char *user,
474: const char *passwd,
475: const char *db,
476: unsigned int port,
477: const char *unix_socket,
478: unsigned int clientflag); t_mysql_real_connect mysql_real_connect;
1.1 parser 479:
1.33 misha 480: typedef void (STDCALL *t_mysql_close)(MYSQL *); t_mysql_close mysql_close;
1.1 parser 481:
482: typedef int (STDCALL *t_mysql_ping)(MYSQL *); t_mysql_ping mysql_ping;
483:
484: typedef unsigned long (STDCALL *t_mysql_escape_string)(char *to,const char *from,
1.33 misha 485: unsigned long from_length); t_mysql_escape_string mysql_escape_string;
1.1 parser 486:
1.33 misha 487: typedef void (STDCALL *t_mysql_free_result)(MYSQL_RES *result); t_mysql_free_result mysql_free_result;
1.1 parser 488:
489: typedef unsigned long* (STDCALL *t_mysql_fetch_lengths)(MYSQL_RES *result); t_mysql_fetch_lengths mysql_fetch_lengths;
490:
491: typedef MYSQL_ROW (STDCALL *t_mysql_fetch_row)(MYSQL_RES *result); t_mysql_fetch_row mysql_fetch_row;
492:
493: typedef MYSQL_FIELD* (STDCALL *t_mysql_fetch_field)(MYSQL_RES *result); t_mysql_fetch_field mysql_fetch_field;
494:
1.33 misha 495: typedef unsigned int (STDCALL *t_mysql_num_fields)(MYSQL_RES *); t_mysql_num_fields mysql_num_fields;
496: typedef unsigned int (STDCALL *t_mysql_field_count)(MYSQL *); t_mysql_field_count mysql_field_count;
1.1 parser 497:
1.33 misha 498: static unsigned int STDCALL subst_mysql_num_fields(MYSQL_RES *res) { return res->field_count; }
499: static unsigned int STDCALL subst_mysql_field_count(MYSQL *mysql) { return mysql->field_count; }
1.1 parser 500:
501: private: // mysql client library funcs linking
502:
503: const char *dlink(const char *dlopen_file_spec) {
1.10 paf 504: if(lt_dlinit())
505: return lt_dlerror();
1.25 paf 506:
1.33 misha 507: lt_dlhandle handle=lt_dlopen(dlopen_file_spec);
508:
509: if(!handle){
510: if(const char* result=lt_dlerror())
511: return result;
1.1 parser 512: return "can not open the dynamic link module";
1.25 paf 513: }
1.1 parser 514:
1.29 misha 515: #define GLINK(name) \
516: name=(t_##name)lt_dlsym(handle, #name);
517:
1.1 parser 518: #define DSLINK(name, action) \
1.29 misha 519: GLINK(name) \
1.1 parser 520: if(!name) \
521: action;
522:
523: #define DLINK(name) DSLINK(name, return "function " #name " was not found")
524: #define SLINK(name) DSLINK(name, name=subst_##name)
525:
526: DLINK(mysql_init);
1.29 misha 527: GLINK(mysql_server_end);
1.2 parser 528: DLINK(mysql_options);
1.1 parser 529: DLINK(mysql_store_result);
530: DLINK(mysql_query);
531: SLINK(mysql_error);
532: DLINK(mysql_real_connect);
533: DLINK(mysql_close);
534: DLINK(mysql_ping);
535: DLINK(mysql_escape_string);
536: DLINK(mysql_free_result);
537: DLINK(mysql_fetch_lengths);
538: DLINK(mysql_fetch_row);
539: DLINK(mysql_fetch_field);
540: SLINK(mysql_num_fields);
541: SLINK(mysql_field_count);
542: return 0;
543: }
544:
545: };
546:
547: extern "C" SQL_Driver *SQL_DRIVER_CREATE() {
1.29 misha 548: static MySQL_Driver Driver;
549: return &Driver;
1.1 parser 550: }
E-mail: