|
|
1.1 parser 1: /** @file
2: Parser ODBC driver.
3:
1.13 paf 4: Copyright(c) 2001, 2003 ArtLebedev Group (http://www.artlebedev.com)
1.1 parser 5:
1.5 paf 6: Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
1.1 parser 7: */
1.37 ! moko 8: static const char *RCSId="$Id: parser3odbc.C,v 1.36 2008-07-08 10:53:32 misha Exp $";
1.1 parser 9:
10: #ifndef _MSC_VER
11: # error compile ISAPI module with MSVC [no urge for now to make it autoconf-ed (PAF)]
12: #endif
13:
14: #include <string.h>
15: #include <stdio.h>
16: #include <stdlib.h>
1.2 paf 17: #include <setjmp.h>
1.1 parser 18:
19: #include "pa_sql_driver.h"
20:
1.16 paf 21: #define WINVER 0x0400
1.1 parser 22: #include <AFXDB.H>
23:
1.10 paf 24: // defines
25:
26: #define MAX_COLS 500
27:
1.1 parser 28: #define MAX_STRING 0x400
1.7 paf 29: #define MAX_NUMBER 40
30:
1.10 paf 31: // new in MSSQL2000, no MFC constants
1.7 paf 32: #ifndef SQL_NVARCHAR
33: #define SQL_NVARCHAR (-9)
34: #endif
35: #ifndef SQL_NTEXT
36: #define SQL_NTEXT (-10)
37: #endif
38: #ifndef SQL_SMALLDATETIME
39: #define SQL_SMALLDATETIME 11
40: #endif
41: // create table test (id int, a smalldatetime, b ntext, c nvarchar(100))
1.1 parser 42:
43: #define snprintf _snprintf
44: #ifndef strncasecmp
45: # define strncasecmp _strnicmp
46: #endif
1.28 misha 47: #ifndef strcasecmp
48: # define strcasecmp _stricmp
49: #endif
1.1 parser 50:
1.27 misha 51: static char *lsplit(char *string, char delim){
52: if(string){
53: if(char* v=strchr(string, delim)){
1.1 parser 54: *v=0;
55: return v+1;
56: }
1.27 misha 57: }
58: return 0;
1.1 parser 59: }
60:
1.28 misha 61: static char *lsplit(char **string_ref, char delim){
62: char *result=*string_ref;
63: char *next=lsplit(*string_ref, delim);
64: *string_ref=next;
65: return result;
66: }
67:
1.27 misha 68: static void toupper_str(char *out, const char *in, size_t size){
1.20 paf 69: while(size--)
70: *out++=(char)toupper(*in++);
71: }
72:
1.33 misha 73: struct modified_statement {
74: const char* statement;
75: bool limit;
76: bool offset;
77: };
78:
79: // todo: MySQL, SQLite, PgSQL (add LIMIT at the end of statement)
80: struct SQL {
81: enum SQLEnum {
82: Undefined,
83: MSSQL,
84: Pervasive,
85: FireBird
86: };
87: };
88:
1.18 paf 89: struct Connection {
90: SQL_Driver_services* services;
91:
92: CDatabase* db;
1.27 misha 93: const char* client_charset;
1.33 misha 94: SQL::SQLEnum sql_specific;
1.27 misha 95: bool autocommit;
1.18 paf 96: };
97:
1.1 parser 98: /**
99: ODBC server driver
100: */
101: class ODBC_Driver : public SQL_Driver {
102: public:
103:
104: ODBC_Driver() : SQL_Driver() {
105: }
106:
107: /// get api version
108: int api_version() { return SQL_DRIVER_API_VERSION; }
1.27 misha 109:
1.3 paf 110: const char *initialize(char *dlopen_file_spec) { return 0; }
1.1 parser 111: /** connect
1.22 paf 112: @param url
1.28 misha 113: format: @b DSN=dsn;UID=user;PWD=password? (ODBC connect string)
114: ClientCharset=charset& // transcode with parser
115: autocommit=1& // 0 -- disable auto commit
1.30 misha 116: FastOffsetSearch=0
1.1 parser 117: WARNING: must be used only to connect, for buffer doesn't live long
118: */
1.27 misha 119:
1.1 parser 120: void connect(
1.27 misha 121: char *url,
122: SQL_Driver_services& services,
123: void **connection_ref ///< output: Connection*
124: ){
125: Connection& connection=*(Connection *)services.malloc(sizeof(Connection));
1.18 paf 126: *connection_ref=&connection;
127: connection.services=&services;
1.27 misha 128: connection.client_charset=0;
1.33 misha 129: connection.sql_specific=SQL::Undefined;
1.27 misha 130: connection.autocommit=true;
131:
132: size_t url_length=strlen(url);
1.28 misha 133: char *options=lsplit(url, '?');
1.20 paf 134:
1.32 misha 135: // todo: analize connect string and if 'SQL Server' found, modify query and add TOP into SELECTs
136:
1.28 misha 137: while(options){
138: if(char *key=lsplit(&options, '&')){
139: if(*key){
140: if(char *value=lsplit(key, '=')){
141: if(strcmp(key, "ClientCharset")==0){
142: toupper_str(value, value, strlen(value));
143: connection.client_charset=value;
144: } else if(strcasecmp(key, "autocommit")==0){
145: if(atoi(value)==0)
146: connection.autocommit=false;
1.33 misha 147: } else if(strcmp(key, "SQL")==0){
148: if(strcasecmp(value, "MSSQL")==0){
149: connection.sql_specific=SQL::MSSQL;
150: } else if(strcasecmp(value, "Pervasive")==0){
151: connection.sql_specific=SQL::Pervasive;
152: } else if(strcasecmp(value, "FireBird")==0){
153: connection.sql_specific=SQL::FireBird;
154: } else {
155: services._throw("unknown value of SQL option was specified" /*key*/);
156: }
1.28 misha 157: } else
158: services._throw("unknown connect option" /*key*/);
159: } else
160: services._throw("connect option without =value" /*key*/);
161: }
1.20 paf 162: }
163: }
1.18 paf 164:
1.1 parser 165: TRY {
1.18 paf 166: connection.db=new CDatabase();
1.22 paf 167: connection.db->OpenEx(url, CDatabase::noOdbcDialog);
1.32 misha 168: connection.db->BeginTrans();
1.1 parser 169: }
170: CATCH_ALL (e) {
171: _throw(services, e);
172: }
173: END_CATCH_ALL
174: }
1.27 misha 175:
176: void disconnect(void *aconnection){
1.18 paf 177: Connection& connection=*static_cast<Connection*>(aconnection);
1.1 parser 178: TRY
1.18 paf 179: delete connection.db;
180: connection.db=0;
1.1 parser 181: CATCH_ALL (e) {
182: // nothing
183: }
184: END_CATCH_ALL
185: }
1.27 misha 186:
187: void commit(void *aconnection){
1.18 paf 188: Connection& connection=*static_cast<Connection*>(aconnection);
1.32 misha 189: TRY
190: connection.db->CommitTrans();
191: connection.db->BeginTrans();
192: CATCH_ALL (e) {
193: _throw(*connection.services, e);
1.1 parser 194: }
1.32 misha 195: END_CATCH_ALL
1.1 parser 196: }
1.27 misha 197:
198: void rollback(void *aconnection){
1.18 paf 199: Connection& connection=*static_cast<Connection*>(aconnection);
1.32 misha 200: TRY
201: connection.db->Rollback();
202: connection.db->BeginTrans();
203: CATCH_ALL (e) {
204: _throw(*connection.services, e);
1.1 parser 205: }
1.32 misha 206: END_CATCH_ALL
1.1 parser 207: }
208:
1.27 misha 209: bool ping(void *connection){
1.1 parser 210: return true;
211: }
212:
1.37 ! moko 213: // charset here is services.request_charset(), not connection.client_charset
! 214: // thus we can't use the sql server quoting support
! 215: const char* quote(void *aconnection, const char *str, unsigned int length)
! 216: {
! 217: const char* from;
! 218: const char* from_end=str+length;
! 219:
! 220: size_t quoted=0;
! 221:
! 222: for(from=str; from<from_end; from++){
! 223: if(*from=='\'')
! 224: quoted++;
! 225: }
! 226:
! 227: if(!quoted)
! 228: return str;
! 229:
1.18 paf 230: Connection& connection=*static_cast<Connection*>(aconnection);
1.37 ! moko 231: char *result=(char*)connection.services->malloc_atomic(length + quoted + 1);
! 232: char *to = result;
! 233:
! 234: for(from=str; from<from_end; from++){
! 235: if(*from=='\'')
! 236: *to++= '\''; // ' -> ''
! 237: *to++=*from;
1.14 paf 238: }
1.37 ! moko 239:
1.14 paf 240: *to=0;
241: return result;
1.1 parser 242: }
1.27 misha 243:
1.18 paf 244: void query(void *aconnection,
1.33 misha 245: const char *astatement,
1.27 misha 246: size_t placeholders_count,
247: Placeholder* placeholders,
248: unsigned long offset,
249: unsigned long limit,
250: SQL_Driver_query_event_handlers& handlers
251: ){
1.18 paf 252: Connection& connection=*static_cast<Connection*>(aconnection);
253: CDatabase *db=connection.db;
254: SQL_Driver_services& services=*connection.services;
1.23 paf 255:
256: if(placeholders_count>0)
257: services._throw("bind variables not supported (yet)");
1.1 parser 258:
1.33 misha 259: while(isspace((unsigned char)*astatement))
260: astatement++;
261:
262: modified_statement mstatement=_preprocess_statement(connection, astatement, offset, limit);
263: const char* statement=mstatement.statement;
264:
1.30 misha 265: const char* client_charset=connection.client_charset;
266: const char* request_charset=services.request_charset();
267: bool transcode_needed=(client_charset && strcmp(client_charset, request_charset)!=0);
1.27 misha 268: if(transcode_needed){
1.33 misha 269: // transcode query from $request:charset to ?ClientCharset
1.27 misha 270: size_t length=strlen(statement);
271: services.transcode(statement, length,
272: statement, length,
1.30 misha 273: request_charset,
274: client_charset);
1.20 paf 275: }
276:
1.1 parser 277: TRY {
1.8 paf 278: // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\adosql.chm::/adoprg02_4g33.htm
1.27 misha 279: // or http://msdn.microsoft.com/en-us/library/aa905899(SQL.80).aspx
1.8 paf 280: // Server cursors are created only for statements that begin with:
281: // SELECT
282: // EXEC[ute] procedure_name
283: // call procedure_name
284: // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\odbcsql.chm::/od_6_035_5dnp.htm
285: // The ODBC CALL escape sequence for calling a procedure is:
286: // {[?=]call procedure_name[([parameter][,[parameter]]...)]}
1.33 misha 287: if(
288: strncasecmp(statement, "SELECT", 6)==0
1.8 paf 289: || strncasecmp(statement, "EXEC", 4)==0
290: || strncasecmp(statement, "call", 4)==0
1.27 misha 291: || strncasecmp(statement, "{", 1)==0
292: ){
293: CRecordset rs(db);
294: DWORD options=CRecordset::executeDirect|CRecordset::readOnly;
1.9 paf 295: TRY {
296: rs.Open(
1.33 misha 297: CRecordset::forwardOnly,
1.9 paf 298: statement,
1.27 misha 299: options
300: );
1.9 paf 301: } CATCH_ALL (e) {
302: // could not fetch a table
303: TRY {
304: // then try resultless query
305: db->ExecuteSQL(statement);
306: // OK then
307: return;
308: } CATCH_ALL (e2) {
309: // still nothing good
310: _throw(services, e); // throw ORIGINAL exception
311: } END_CATCH_ALL
312: } END_CATCH_ALL
1.1 parser 313:
314: int column_count=rs.GetODBCFieldCount();
315: if(!column_count)
316: services._throw("result contains no columns");
317:
1.10 paf 318: if(column_count>MAX_COLS)
319: column_count=MAX_COLS;
320:
1.27 misha 321: SWORD column_types[MAX_COLS];
1.30 misha 322: bool transcode_column[MAX_COLS];
1.27 misha 323:
1.12 paf 324: SQL_Error sql_error;
325: #define CHECK(afailed) if(afailed) services._throw(sql_error)
326:
1.1 parser 327: for(int i=0; i<column_count; i++){
328: CString string;
329: CODBCFieldInfo fieldinfo;
330: rs.GetODBCFieldInfo(i, fieldinfo);
1.10 paf 331: column_types[i]=fieldinfo.m_nSQLType;
1.30 misha 332: switch(fieldinfo.m_nSQLType){
333: case SQL_NUMERIC:
334: case SQL_DECIMAL:
335: case SQL_INTEGER:
336: case SQL_SMALLINT:
337: case SQL_FLOAT:
338: case SQL_REAL:
339: case SQL_DOUBLE:
340: case SQL_DATETIME:
341: case SQL_SMALLDATETIME:
342: case SQL_BIGINT:
343: case SQL_TINYINT:
344: transcode_column[i]=false;
345: break;
346: default:
347: transcode_column[i]=transcode_needed;
1.32 misha 348: break;
1.30 misha 349: }
1.20 paf 350: size_t length=fieldinfo.m_strName.GetLength();
1.14 paf 351: char *str=0;
1.27 misha 352: if(length){
1.20 paf 353: str=(char*)services.malloc_atomic(length+1);
1.27 misha 354: memcpy(str, (char*)LPCTSTR(fieldinfo.m_strName), length+1);
1.20 paf 355:
1.27 misha 356: // transcode column name from ?ClientCharset to $request:charset
357: if(transcode_needed){
1.20 paf 358: services.transcode(str, length,
359: str, length,
1.30 misha 360: client_charset,
361: request_charset);
1.20 paf 362: }
1.1 parser 363: }
1.20 paf 364: CHECK(handlers.add_column(sql_error, str, length));
1.1 parser 365: }
366:
1.12 paf 367: CHECK(handlers.before_rows(sql_error));
1.27 misha 368:
369: // skip offset rows
1.36 misha 370: if(offset && limit!=0 && !mstatement.offset){
1.33 misha 371: unsigned long row=offset;
372: while(!rs.IsEOF() && row--)
373: rs.MoveNext();
1.27 misha 374: }
1.1 parser 375:
376: unsigned long row=0;
1.10 paf 377: CDBVariant v;
378: CString s;
1.27 misha 379: while(!rs.IsEOF() && (limit==SQL_NO_LIMIT || row<limit)){
380: CHECK(handlers.add_row(sql_error));
381: for(int i=0; i<column_count; i++){
382: size_t length;
383: char* str;
384: switch(column_types[i]){
1.7 paf 385: //case xBOOL:
1.14 paf 386: //case SQL_DATETIME: << default: handles that more properly (?)
387: case SQL_BINARY:
1.11 paf 388: case SQL_VARBINARY:
389: case SQL_LONGVARBINARY:
1.7 paf 390: case SQL_SMALLDATETIME:
1.27 misha 391: //case SQL_NVARCHAR: // mfc 7.1 has errors with nvarchar(length): SQLGetData in dbcore.cpp truncates last byte for unknown reason.
392: // could be fixed by uncommenting this and handing DBVT_WSTRING inside, but it's UNICODE
1.10 paf 393: rs.GetFieldValue(i, v);
1.20 paf 394: getFromDBVariant(services, v, str, length);
1.10 paf 395: break;
1.7 paf 396: default:
1.10 paf 397: rs.GetFieldValue(i, s);
1.20 paf 398: getFromString(services, s, str, length);
1.10 paf 399: break;
1.27 misha 400: }
1.20 paf 401:
1.27 misha 402: // transcode cell value from ?ClientCharset to $request:charset
1.30 misha 403: if(length && transcode_column[i]){
1.27 misha 404: services.transcode(str, length,
405: str, length,
1.30 misha 406: client_charset,
407: request_charset);
408: }
1.20 paf 409:
1.27 misha 410: CHECK(handlers.add_row_cell(sql_error, str, length));
1.1 parser 411: }
1.27 misha 412: rs.MoveNext();
413: row++;
1.1 parser 414: }
415:
416: rs.Close();
417: } else {
418: db->ExecuteSQL(statement);
419: }
1.9 paf 420: } CATCH_ALL (e) {
1.1 parser 421: _throw(services, e);
1.9 paf 422: } END_CATCH_ALL
1.32 misha 423:
424: if(connection.autocommit)
425: commit(aconnection);
1.7 paf 426: }
427:
1.27 misha 428: private:
429: void getFromDBVariant(SQL_Driver_services& services, CDBVariant& v, char*& str, size_t& length){
430: switch(v.m_dwType){
1.14 paf 431: case DBVT_BINARY: /* << would cause problems with current String implementation
432: now falling into NULL case, effectively ignoring such columns [not failing]
433: {
1.17 paf 434: if(length=v.m_pbinary->m_dwDataLength) {
435: str=services.malloc_atomic(length+1);
436: memcpy(ptr, ::GlobalLock(v.m_pbinary->m_hData), length);
1.14 paf 437: ::GlobalUnlock(v.m_pbinary->m_hData);
438: } else
439: str=0;
440: break;
441: }*/
1.7 paf 442: case DBVT_NULL: // No union member is valid for access.
1.14 paf 443: str=0;
1.17 paf 444: length=0;
1.7 paf 445: break;
446: /* case DBVT_BOOL:
447: ptr=v.m_boolVal?"1":"0";
1.17 paf 448: length=1;
1.7 paf 449: break;*/
1.32 misha 450: /* case DBVT_UCHAR:
1.17 paf 451: length=strlen(ptr=v.m_chVal);
1.7 paf 452: break;
453: case DBVT_SHORT:
454: char buf[MAX_NUMBER];
1.17 paf 455: length=snprintf(HEAPIZE buf, "%d", v.m_iVal);
1.27 misha 456: break;
457: */
1.7 paf 458: /* case DBVT_LONG:
459: {
460: char local_buf[MAX_NUMBER];
1.17 paf 461: length=snprintf(local_buf, MAX_NUMBER, "%ld", v.m_lVal);
462: ptr=services.malloc_atomic(length);
463: memcpy(ptr, local_buf, length);
1.7 paf 464: break;
1.27 misha 465: }
466: */
467: /*
468: case DBVT_SINGLE:
1.7 paf 469: m_fltVal
470: break;
1.27 misha 471: case DBVT_DOUBLE m_dblVal
472: case DBVT_STRING m_pstring
473: */
1.7 paf 474: case DBVT_DATE:
475: {
476: char local_buf[MAX_STRING];
1.17 paf 477: length=snprintf(local_buf, MAX_STRING,
1.7 paf 478: "%04d-%02d-%02d %02d:%02d:%02d.%03d",
479: v.m_pdate->year,
480: v.m_pdate->month,
481: v.m_pdate->day,
482: v.m_pdate->hour,
483: v.m_pdate->minute,
484: v.m_pdate->second,
1.25 paf 485: v.m_pdate->fraction/1000000); // lexical parser of INCOMING literal choked on times like hh:mm:ss.123000000
1.17 paf 486: str=(char*)services.malloc_atomic(length+1);
487: memcpy(str, local_buf, length+1);
1.7 paf 488: break;
489: }
490: default:
491: char msg[MAX_STRING];
492: snprintf(msg, MAX_STRING, "unknown column return variant type (%d)",
493: v.m_dwType);
494: services._throw(msg);
495: }
496: }
497:
1.27 misha 498: void getFromString(SQL_Driver_services& services, CString& s, char*& astr, size_t& length){
499: if(s.IsEmpty()){
1.14 paf 500: astr=0;
1.17 paf 501: length=0;
1.7 paf 502: } else {
503: const char *cstr=LPCTSTR(s);
1.17 paf 504: length=strlen(cstr); //string.GetLength() works wrong with non-string types:
505: astr=(char*)services.malloc_atomic(length+1);
506: memcpy(astr, cstr, length+1);
1.7 paf 507: }
1.1 parser 508: }
509:
1.33 misha 510: modified_statement _preprocess_statement(
511: Connection& connection,
512: const char* astatement,
513: unsigned long offset,
514: unsigned long limit
515: ){
516: modified_statement result={astatement, false, false};
517:
518: if(limit!=SQL_NO_LIMIT && connection.sql_specific!=SQL::Undefined && strncasecmp(astatement, "select", 6)==0){
519: switch(connection.sql_specific){
520: case SQL::MSSQL:
521: case SQL::Pervasive: // uses TOP as well
522: {
523: // add ' TOP limit+offset' after 'SELECT'
524: char* statement_limited=(char *)connection.services->malloc_atomic(
525: strlen(astatement)
526: +MAX_NUMBER
527: +5/* TOP */
528: +1/*terminator*/
529: );
530:
1.35 misha 531: result.limit=true;
1.33 misha 532: result.statement=statement_limited;
533:
1.35 misha 534: snprintf(statement_limited, MAX_NUMBER+11, "SELECT TOP %u", (limit)?limit+offset:0/*no point to skip anything if we need 0 rows*/);
1.33 misha 535:
536: astatement+=6;/*skip 'select'*/
537: strcat(statement_limited, astatement);
538:
539: //connection.services->_throw(result.statement);
540: break;
541: }
542: case SQL::FireBird:
543: {
1.34 misha 544: // add ' FIRST (limit) SKIP (offset)' after 'SELECT'
1.33 misha 545: char* statement_limited=(char *)connection.services->malloc_atomic(
546: strlen(astatement)
547: +MAX_NUMBER*2
548: +9/* FIRST ()*/
549: +offset?8:0/* SKIP ()*/
550: +1/*terminator*/
551: );
552:
553: result.limit=true;
554: result.offset=true;
555: result.statement=statement_limited;
556:
557: statement_limited+=snprintf(statement_limited, MAX_NUMBER+15, "SELECT FIRST (%u)", limit);
558: if(offset && limit/*no reasons to skip something if we need 0 rows*/)
559: statement_limited+=snprintf(statement_limited, MAX_NUMBER+8, " SKIP (%u)", offset);
560:
561: astatement+=6;/*skip 'select'*/
562: strcat((char*)result.statement, astatement);
563:
564: //connection.services->_throw(result.statement);
565: break;
566: }
567: default:
1.34 misha 568: connection.services->_throw("Unknown SQL specifics");
1.33 misha 569: }
570: }
571: return result;
572: }
573:
1.27 misha 574: void _throw(SQL_Driver_services& services, CException *e){
575: char szCause[MAX_STRING];
576: szCause[0]=0;
1.1 parser 577: e->GetErrorMessage(szCause, MAX_STRING);
578: char msg[MAX_STRING];
579: snprintf(msg, MAX_STRING, "%s: %s",
580: e->GetRuntimeClass()->m_lpszClassName,
581: *szCause?szCause:"unknown");
582: services._throw(msg);
583: }
584:
1.32 misha 585: void _throw(Connection& connection, long value){
586: char msg[MAX_STRING];
587: snprintf(msg, MAX_STRING, "%u", value);
588: connection.services->_throw(msg);
589: }
590:
1.1 parser 591: };
592:
593: extern "C" SQL_Driver *SQL_DRIVER_CREATE() {
594: return new ODBC_Driver();
1.26 paf 595: }