|
|
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.33 misha 8: static const char *RCSId="$Id: parser3odbc.C,v 1.32 2008-07-01 10:21:03 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.27 misha 213: const char* quote(void *aconnection, const char *from, unsigned int length){
1.18 paf 214: Connection& connection=*static_cast<Connection*>(aconnection);
215: char *result=(char*)connection.services->malloc_atomic(length*2+1);
1.14 paf 216: char *to=result;
1.27 misha 217: while(length--){
1.14 paf 218: if(*from=='\'') { // ' -> ''
1.15 paf 219: *to++='\'';
1.3 paf 220: }
1.14 paf 221: *to++=*from++;
222: }
223: *to=0;
224: return result;
1.1 parser 225: }
1.27 misha 226:
1.18 paf 227: void query(void *aconnection,
1.33 misha 228: const char *astatement,
1.27 misha 229: size_t placeholders_count,
230: Placeholder* placeholders,
231: unsigned long offset,
232: unsigned long limit,
233: SQL_Driver_query_event_handlers& handlers
234: ){
1.18 paf 235: Connection& connection=*static_cast<Connection*>(aconnection);
236: CDatabase *db=connection.db;
237: SQL_Driver_services& services=*connection.services;
1.23 paf 238:
239: if(placeholders_count>0)
240: services._throw("bind variables not supported (yet)");
1.1 parser 241:
1.33 misha 242: while(isspace((unsigned char)*astatement))
243: astatement++;
244:
245: modified_statement mstatement=_preprocess_statement(connection, astatement, offset, limit);
246: const char* statement=mstatement.statement;
247:
1.30 misha 248: const char* client_charset=connection.client_charset;
249: const char* request_charset=services.request_charset();
250: bool transcode_needed=(client_charset && strcmp(client_charset, request_charset)!=0);
1.27 misha 251: if(transcode_needed){
1.33 misha 252: // transcode query from $request:charset to ?ClientCharset
1.27 misha 253: size_t length=strlen(statement);
254: services.transcode(statement, length,
255: statement, length,
1.30 misha 256: request_charset,
257: client_charset);
1.20 paf 258: }
259:
1.1 parser 260: TRY {
1.8 paf 261: // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\adosql.chm::/adoprg02_4g33.htm
1.27 misha 262: // or http://msdn.microsoft.com/en-us/library/aa905899(SQL.80).aspx
1.8 paf 263: // Server cursors are created only for statements that begin with:
264: // SELECT
265: // EXEC[ute] procedure_name
266: // call procedure_name
267: // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\odbcsql.chm::/od_6_035_5dnp.htm
268: // The ODBC CALL escape sequence for calling a procedure is:
269: // {[?=]call procedure_name[([parameter][,[parameter]]...)]}
1.33 misha 270: if(
271: strncasecmp(statement, "SELECT", 6)==0
1.8 paf 272: || strncasecmp(statement, "EXEC", 4)==0
273: || strncasecmp(statement, "call", 4)==0
1.27 misha 274: || strncasecmp(statement, "{", 1)==0
275: ){
276: CRecordset rs(db);
277: DWORD options=CRecordset::executeDirect|CRecordset::readOnly;
1.9 paf 278: TRY {
279: rs.Open(
1.33 misha 280: CRecordset::forwardOnly,
1.9 paf 281: statement,
1.27 misha 282: options
283: );
1.9 paf 284: } CATCH_ALL (e) {
285: // could not fetch a table
286: TRY {
287: // then try resultless query
288: db->ExecuteSQL(statement);
289: // OK then
290: return;
291: } CATCH_ALL (e2) {
292: // still nothing good
293: _throw(services, e); // throw ORIGINAL exception
294: } END_CATCH_ALL
295: } END_CATCH_ALL
1.1 parser 296:
297: int column_count=rs.GetODBCFieldCount();
298: if(!column_count)
299: services._throw("result contains no columns");
300:
1.10 paf 301: if(column_count>MAX_COLS)
302: column_count=MAX_COLS;
303:
1.27 misha 304: SWORD column_types[MAX_COLS];
1.30 misha 305: bool transcode_column[MAX_COLS];
1.27 misha 306:
1.12 paf 307: SQL_Error sql_error;
308: #define CHECK(afailed) if(afailed) services._throw(sql_error)
309:
1.1 parser 310: for(int i=0; i<column_count; i++){
311: CString string;
312: CODBCFieldInfo fieldinfo;
313: rs.GetODBCFieldInfo(i, fieldinfo);
1.10 paf 314: column_types[i]=fieldinfo.m_nSQLType;
1.30 misha 315: switch(fieldinfo.m_nSQLType){
316: case SQL_NUMERIC:
317: case SQL_DECIMAL:
318: case SQL_INTEGER:
319: case SQL_SMALLINT:
320: case SQL_FLOAT:
321: case SQL_REAL:
322: case SQL_DOUBLE:
323: case SQL_DATETIME:
324: case SQL_SMALLDATETIME:
325: case SQL_BIGINT:
326: case SQL_TINYINT:
327: transcode_column[i]=false;
328: break;
329: default:
330: transcode_column[i]=transcode_needed;
1.32 misha 331: break;
1.30 misha 332: }
1.20 paf 333: size_t length=fieldinfo.m_strName.GetLength();
1.14 paf 334: char *str=0;
1.27 misha 335: if(length){
1.20 paf 336: str=(char*)services.malloc_atomic(length+1);
1.27 misha 337: memcpy(str, (char*)LPCTSTR(fieldinfo.m_strName), length+1);
1.20 paf 338:
1.27 misha 339: // transcode column name from ?ClientCharset to $request:charset
340: if(transcode_needed){
1.20 paf 341: services.transcode(str, length,
342: str, length,
1.30 misha 343: client_charset,
344: request_charset);
1.20 paf 345: }
1.1 parser 346: }
1.20 paf 347: CHECK(handlers.add_column(sql_error, str, length));
1.1 parser 348: }
349:
1.12 paf 350: CHECK(handlers.before_rows(sql_error));
1.27 misha 351:
352: // skip offset rows
1.36 ! misha 353: if(offset && limit!=0 && !mstatement.offset){
1.33 misha 354: unsigned long row=offset;
355: while(!rs.IsEOF() && row--)
356: rs.MoveNext();
1.27 misha 357: }
1.1 parser 358:
359: unsigned long row=0;
1.10 paf 360: CDBVariant v;
361: CString s;
1.27 misha 362: while(!rs.IsEOF() && (limit==SQL_NO_LIMIT || row<limit)){
363: CHECK(handlers.add_row(sql_error));
364: for(int i=0; i<column_count; i++){
365: size_t length;
366: char* str;
367: switch(column_types[i]){
1.7 paf 368: //case xBOOL:
1.14 paf 369: //case SQL_DATETIME: << default: handles that more properly (?)
370: case SQL_BINARY:
1.11 paf 371: case SQL_VARBINARY:
372: case SQL_LONGVARBINARY:
1.7 paf 373: case SQL_SMALLDATETIME:
1.27 misha 374: //case SQL_NVARCHAR: // mfc 7.1 has errors with nvarchar(length): SQLGetData in dbcore.cpp truncates last byte for unknown reason.
375: // could be fixed by uncommenting this and handing DBVT_WSTRING inside, but it's UNICODE
1.10 paf 376: rs.GetFieldValue(i, v);
1.20 paf 377: getFromDBVariant(services, v, str, length);
1.10 paf 378: break;
1.7 paf 379: default:
1.10 paf 380: rs.GetFieldValue(i, s);
1.20 paf 381: getFromString(services, s, str, length);
1.10 paf 382: break;
1.27 misha 383: }
1.20 paf 384:
1.27 misha 385: // transcode cell value from ?ClientCharset to $request:charset
1.30 misha 386: if(length && transcode_column[i]){
1.27 misha 387: services.transcode(str, length,
388: str, length,
1.30 misha 389: client_charset,
390: request_charset);
391: }
1.20 paf 392:
1.27 misha 393: CHECK(handlers.add_row_cell(sql_error, str, length));
1.1 parser 394: }
1.27 misha 395: rs.MoveNext();
396: row++;
1.1 parser 397: }
398:
399: rs.Close();
400: } else {
401: db->ExecuteSQL(statement);
402: }
1.9 paf 403: } CATCH_ALL (e) {
1.1 parser 404: _throw(services, e);
1.9 paf 405: } END_CATCH_ALL
1.32 misha 406:
407: if(connection.autocommit)
408: commit(aconnection);
1.7 paf 409: }
410:
1.27 misha 411: private:
412: void getFromDBVariant(SQL_Driver_services& services, CDBVariant& v, char*& str, size_t& length){
413: switch(v.m_dwType){
1.14 paf 414: case DBVT_BINARY: /* << would cause problems with current String implementation
415: now falling into NULL case, effectively ignoring such columns [not failing]
416: {
1.17 paf 417: if(length=v.m_pbinary->m_dwDataLength) {
418: str=services.malloc_atomic(length+1);
419: memcpy(ptr, ::GlobalLock(v.m_pbinary->m_hData), length);
1.14 paf 420: ::GlobalUnlock(v.m_pbinary->m_hData);
421: } else
422: str=0;
423: break;
424: }*/
1.7 paf 425: case DBVT_NULL: // No union member is valid for access.
1.14 paf 426: str=0;
1.17 paf 427: length=0;
1.7 paf 428: break;
429: /* case DBVT_BOOL:
430: ptr=v.m_boolVal?"1":"0";
1.17 paf 431: length=1;
1.7 paf 432: break;*/
1.32 misha 433: /* case DBVT_UCHAR:
1.17 paf 434: length=strlen(ptr=v.m_chVal);
1.7 paf 435: break;
436: case DBVT_SHORT:
437: char buf[MAX_NUMBER];
1.17 paf 438: length=snprintf(HEAPIZE buf, "%d", v.m_iVal);
1.27 misha 439: break;
440: */
1.7 paf 441: /* case DBVT_LONG:
442: {
443: char local_buf[MAX_NUMBER];
1.17 paf 444: length=snprintf(local_buf, MAX_NUMBER, "%ld", v.m_lVal);
445: ptr=services.malloc_atomic(length);
446: memcpy(ptr, local_buf, length);
1.7 paf 447: break;
1.27 misha 448: }
449: */
450: /*
451: case DBVT_SINGLE:
1.7 paf 452: m_fltVal
453: break;
1.27 misha 454: case DBVT_DOUBLE m_dblVal
455: case DBVT_STRING m_pstring
456: */
1.7 paf 457: case DBVT_DATE:
458: {
459: char local_buf[MAX_STRING];
1.17 paf 460: length=snprintf(local_buf, MAX_STRING,
1.7 paf 461: "%04d-%02d-%02d %02d:%02d:%02d.%03d",
462: v.m_pdate->year,
463: v.m_pdate->month,
464: v.m_pdate->day,
465: v.m_pdate->hour,
466: v.m_pdate->minute,
467: v.m_pdate->second,
1.25 paf 468: v.m_pdate->fraction/1000000); // lexical parser of INCOMING literal choked on times like hh:mm:ss.123000000
1.17 paf 469: str=(char*)services.malloc_atomic(length+1);
470: memcpy(str, local_buf, length+1);
1.7 paf 471: break;
472: }
473: default:
474: char msg[MAX_STRING];
475: snprintf(msg, MAX_STRING, "unknown column return variant type (%d)",
476: v.m_dwType);
477: services._throw(msg);
478: }
479: }
480:
1.27 misha 481: void getFromString(SQL_Driver_services& services, CString& s, char*& astr, size_t& length){
482: if(s.IsEmpty()){
1.14 paf 483: astr=0;
1.17 paf 484: length=0;
1.7 paf 485: } else {
486: const char *cstr=LPCTSTR(s);
1.17 paf 487: length=strlen(cstr); //string.GetLength() works wrong with non-string types:
488: astr=(char*)services.malloc_atomic(length+1);
489: memcpy(astr, cstr, length+1);
1.7 paf 490: }
1.1 parser 491: }
492:
1.33 misha 493: modified_statement _preprocess_statement(
494: Connection& connection,
495: const char* astatement,
496: unsigned long offset,
497: unsigned long limit
498: ){
499: modified_statement result={astatement, false, false};
500:
501: if(limit!=SQL_NO_LIMIT && connection.sql_specific!=SQL::Undefined && strncasecmp(astatement, "select", 6)==0){
502: switch(connection.sql_specific){
503: case SQL::MSSQL:
504: case SQL::Pervasive: // uses TOP as well
505: {
506: // add ' TOP limit+offset' after 'SELECT'
507: char* statement_limited=(char *)connection.services->malloc_atomic(
508: strlen(astatement)
509: +MAX_NUMBER
510: +5/* TOP */
511: +1/*terminator*/
512: );
513:
1.35 misha 514: result.limit=true;
1.33 misha 515: result.statement=statement_limited;
516:
1.35 misha 517: 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 518:
519: astatement+=6;/*skip 'select'*/
520: strcat(statement_limited, astatement);
521:
522: //connection.services->_throw(result.statement);
523: break;
524: }
525: case SQL::FireBird:
526: {
1.34 misha 527: // add ' FIRST (limit) SKIP (offset)' after 'SELECT'
1.33 misha 528: char* statement_limited=(char *)connection.services->malloc_atomic(
529: strlen(astatement)
530: +MAX_NUMBER*2
531: +9/* FIRST ()*/
532: +offset?8:0/* SKIP ()*/
533: +1/*terminator*/
534: );
535:
536: result.limit=true;
537: result.offset=true;
538: result.statement=statement_limited;
539:
540: statement_limited+=snprintf(statement_limited, MAX_NUMBER+15, "SELECT FIRST (%u)", limit);
541: if(offset && limit/*no reasons to skip something if we need 0 rows*/)
542: statement_limited+=snprintf(statement_limited, MAX_NUMBER+8, " SKIP (%u)", offset);
543:
544: astatement+=6;/*skip 'select'*/
545: strcat((char*)result.statement, astatement);
546:
547: //connection.services->_throw(result.statement);
548: break;
549: }
550: default:
1.34 misha 551: connection.services->_throw("Unknown SQL specifics");
1.33 misha 552: }
553: }
554: return result;
555: }
556:
1.27 misha 557: void _throw(SQL_Driver_services& services, CException *e){
558: char szCause[MAX_STRING];
559: szCause[0]=0;
1.1 parser 560: e->GetErrorMessage(szCause, MAX_STRING);
561: char msg[MAX_STRING];
562: snprintf(msg, MAX_STRING, "%s: %s",
563: e->GetRuntimeClass()->m_lpszClassName,
564: *szCause?szCause:"unknown");
565: services._throw(msg);
566: }
567:
1.32 misha 568: void _throw(Connection& connection, long value){
569: char msg[MAX_STRING];
570: snprintf(msg, MAX_STRING, "%u", value);
571: connection.services->_throw(msg);
572: }
573:
1.1 parser 574: };
575:
576: extern "C" SQL_Driver *SQL_DRIVER_CREATE() {
577: return new ODBC_Driver();
1.26 paf 578: }