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