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