|
|
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.27 misha 8: static const char *RCSId="$Id: parser3odbc.C,v 1.26 2004/09/13 14:47:43 paf 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.18 paf 73: struct Connection {
74: SQL_Driver_services* services;
75:
76: CDatabase* db;
1.27 misha 77: const char* client_charset;
78: bool autocommit;
1.28 misha 79: bool fast_offset_search;
1.18 paf 80: };
81:
1.1 parser 82: /**
83: ODBC server driver
84: */
85: class ODBC_Driver : public SQL_Driver {
86: public:
87:
88: ODBC_Driver() : SQL_Driver() {
89: }
90:
91: /// get api version
92: int api_version() { return SQL_DRIVER_API_VERSION; }
1.27 misha 93:
1.3 paf 94: const char *initialize(char *dlopen_file_spec) { return 0; }
1.1 parser 95: /** connect
1.22 paf 96: @param url
1.28 misha 97: format: @b DSN=dsn;UID=user;PWD=password? (ODBC connect string)
98: ClientCharset=charset& // transcode with parser
99: autocommit=1& // 0 -- disable auto commit
1.30 misha 100: FastOffsetSearch=0
1.1 parser 101: WARNING: must be used only to connect, for buffer doesn't live long
102: */
1.27 misha 103:
1.1 parser 104: void connect(
1.27 misha 105: char *url,
106: SQL_Driver_services& services,
107: void **connection_ref ///< output: Connection*
108: ){
109: Connection& connection=*(Connection *)services.malloc(sizeof(Connection));
1.18 paf 110: *connection_ref=&connection;
111: connection.services=&services;
1.27 misha 112: connection.client_charset=0;
1.29 misha 113: connection.fast_offset_search=false;
1.27 misha 114: connection.autocommit=true;
115:
116: size_t url_length=strlen(url);
1.28 misha 117: char *options=lsplit(url, '?');
1.20 paf 118:
1.32 ! misha 119: // todo: analize connect string and if 'SQL Server' found, modify query and add TOP into SELECTs
! 120:
1.28 misha 121: while(options){
122: if(char *key=lsplit(&options, '&')){
123: if(*key){
124: if(char *value=lsplit(key, '=')){
125: if(strcmp(key, "ClientCharset")==0){
126: toupper_str(value, value, strlen(value));
127: connection.client_charset=value;
128: } else if(strcasecmp(key, "autocommit")==0){
129: if(atoi(value)==0)
130: connection.autocommit=false;
131: } else if(strcmp(key, "FastOffsetSearch")==0){
1.29 misha 132: if(atoi(value)==1)
133: connection.fast_offset_search=true;
1.28 misha 134: } else
135: services._throw("unknown connect option" /*key*/);
136: } else
137: services._throw("connect option without =value" /*key*/);
138: }
1.20 paf 139: }
140: }
1.18 paf 141:
1.1 parser 142: TRY {
1.18 paf 143: connection.db=new CDatabase();
1.22 paf 144: connection.db->OpenEx(url, CDatabase::noOdbcDialog);
1.32 ! misha 145: connection.db->BeginTrans();
1.1 parser 146: }
147: CATCH_ALL (e) {
148: _throw(services, e);
149: }
150: END_CATCH_ALL
151: }
1.27 misha 152:
153: void disconnect(void *aconnection){
1.18 paf 154: Connection& connection=*static_cast<Connection*>(aconnection);
1.1 parser 155: TRY
1.18 paf 156: delete connection.db;
157: connection.db=0;
1.1 parser 158: CATCH_ALL (e) {
159: // nothing
160: }
161: END_CATCH_ALL
162: }
1.27 misha 163:
164: void commit(void *aconnection){
1.18 paf 165: Connection& connection=*static_cast<Connection*>(aconnection);
1.32 ! misha 166: TRY
! 167: connection.db->CommitTrans();
! 168: connection.db->BeginTrans();
! 169: CATCH_ALL (e) {
! 170: _throw(*connection.services, e);
1.1 parser 171: }
1.32 ! misha 172: END_CATCH_ALL
1.1 parser 173: }
1.27 misha 174:
175: void rollback(void *aconnection){
1.18 paf 176: Connection& connection=*static_cast<Connection*>(aconnection);
1.32 ! misha 177: TRY
! 178: connection.db->Rollback();
! 179: connection.db->BeginTrans();
! 180: CATCH_ALL (e) {
! 181: _throw(*connection.services, e);
1.1 parser 182: }
1.32 ! misha 183: END_CATCH_ALL
1.1 parser 184: }
185:
1.27 misha 186: bool ping(void *connection){
1.1 parser 187: return true;
188: }
189:
1.27 misha 190: const char* quote(void *aconnection, const char *from, unsigned int length){
1.18 paf 191: Connection& connection=*static_cast<Connection*>(aconnection);
192: char *result=(char*)connection.services->malloc_atomic(length*2+1);
1.14 paf 193: char *to=result;
1.27 misha 194: while(length--){
1.14 paf 195: if(*from=='\'') { // ' -> ''
1.15 paf 196: *to++='\'';
1.3 paf 197: }
1.14 paf 198: *to++=*from++;
199: }
200: *to=0;
201: return result;
1.1 parser 202: }
1.27 misha 203:
1.18 paf 204: void query(void *aconnection,
1.27 misha 205: const char *statement,
206: size_t placeholders_count,
207: Placeholder* placeholders,
208: unsigned long offset,
209: unsigned long limit,
210: SQL_Driver_query_event_handlers& handlers
211: ){
1.18 paf 212: Connection& connection=*static_cast<Connection*>(aconnection);
213: CDatabase *db=connection.db;
214: SQL_Driver_services& services=*connection.services;
1.23 paf 215:
216: if(placeholders_count>0)
217: services._throw("bind variables not supported (yet)");
1.1 parser 218:
1.30 misha 219: const char* client_charset=connection.client_charset;
220: const char* request_charset=services.request_charset();
221: bool transcode_needed=(client_charset && strcmp(client_charset, request_charset)!=0);
1.27 misha 222:
223: // transcode query from $request:charset to ?ClientCharset
224: if(transcode_needed){
225: size_t length=strlen(statement);
226: services.transcode(statement, length,
227: statement, length,
1.30 misha 228: request_charset,
229: client_charset);
1.20 paf 230: }
231:
1.24 paf 232: while(isspace((unsigned char)*statement))
1.1 parser 233: statement++;
1.27 misha 234:
1.1 parser 235: TRY {
1.8 paf 236: // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\adosql.chm::/adoprg02_4g33.htm
1.27 misha 237: // or http://msdn.microsoft.com/en-us/library/aa905899(SQL.80).aspx
1.8 paf 238: // Server cursors are created only for statements that begin with:
239: // SELECT
240: // EXEC[ute] procedure_name
241: // call procedure_name
242: // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\odbcsql.chm::/od_6_035_5dnp.htm
243: // The ODBC CALL escape sequence for calling a procedure is:
244: // {[?=]call procedure_name[([parameter][,[parameter]]...)]}
1.27 misha 245: if(strncasecmp(statement, "SELECT", 6)==0
1.8 paf 246: || strncasecmp(statement, "EXEC", 4)==0
247: || strncasecmp(statement, "call", 4)==0
1.27 misha 248: || strncasecmp(statement, "{", 1)==0
249: ){
250: CRecordset rs(db);
251: DWORD options=CRecordset::executeDirect|CRecordset::readOnly;
252: //CRecordset::skipDeletedRecords
253: //CRecordset::useMultiRowFetch
254: //CRecordset::userAllocMultiRowBuffers
255: //CRecordset::useExtendedFetch
1.28 misha 256: /*
257: if(connection.fast_offset_search){
1.27 misha 258: options+=CRecordset::useMultiRowFetch+CRecordset::userAllocMultiRowBuffers;
259: } else {
260: options+=CRecordset::skipDeletedRecords;
261: }
1.28 misha 262: */
1.9 paf 263: TRY {
264: rs.Open(
1.28 misha 265: (connection.fast_offset_search)?CRecordset::dynamic:CRecordset::forwardOnly,
1.9 paf 266: statement,
1.27 misha 267: options
268: );
1.9 paf 269: } CATCH_ALL (e) {
270: // could not fetch a table
271: TRY {
272: // then try resultless query
273: db->ExecuteSQL(statement);
274: // OK then
275: return;
276: } CATCH_ALL (e2) {
277: // still nothing good
278: _throw(services, e); // throw ORIGINAL exception
279: } END_CATCH_ALL
280: } END_CATCH_ALL
1.1 parser 281:
282: int column_count=rs.GetODBCFieldCount();
283: if(!column_count)
284: services._throw("result contains no columns");
285:
1.10 paf 286: if(column_count>MAX_COLS)
287: column_count=MAX_COLS;
288:
1.27 misha 289: SWORD column_types[MAX_COLS];
1.30 misha 290: bool transcode_column[MAX_COLS];
1.27 misha 291:
1.12 paf 292: SQL_Error sql_error;
293: #define CHECK(afailed) if(afailed) services._throw(sql_error)
294:
1.1 parser 295: for(int i=0; i<column_count; i++){
296: CString string;
297: CODBCFieldInfo fieldinfo;
298: rs.GetODBCFieldInfo(i, fieldinfo);
1.10 paf 299: column_types[i]=fieldinfo.m_nSQLType;
1.30 misha 300: switch(fieldinfo.m_nSQLType){
301: case SQL_NUMERIC:
302: case SQL_DECIMAL:
303: case SQL_INTEGER:
304: case SQL_SMALLINT:
305: case SQL_FLOAT:
306: case SQL_REAL:
307: case SQL_DOUBLE:
308: case SQL_DATETIME:
309: case SQL_SMALLDATETIME:
310: case SQL_BIGINT:
311: case SQL_TINYINT:
312: transcode_column[i]=false;
313: break;
314: default:
315: transcode_column[i]=transcode_needed;
1.32 ! misha 316: break;
1.30 misha 317: }
1.20 paf 318: size_t length=fieldinfo.m_strName.GetLength();
1.14 paf 319: char *str=0;
1.27 misha 320: if(length){
1.20 paf 321: str=(char*)services.malloc_atomic(length+1);
1.27 misha 322: memcpy(str, (char*)LPCTSTR(fieldinfo.m_strName), length+1);
1.20 paf 323:
1.27 misha 324: // transcode column name from ?ClientCharset to $request:charset
325: if(transcode_needed){
1.20 paf 326: services.transcode(str, length,
327: str, length,
1.30 misha 328: client_charset,
329: request_charset);
1.20 paf 330: }
1.1 parser 331: }
1.20 paf 332: CHECK(handlers.add_column(sql_error, str, length));
1.1 parser 333: }
334:
1.12 paf 335: CHECK(handlers.before_rows(sql_error));
1.27 misha 336:
337: // skip offset rows
338: if(offset){
1.28 misha 339: if(connection.fast_offset_search){
1.27 misha 340: rs.Move(offset);
341: } else {
342: unsigned long row=offset;
1.32 ! misha 343: while(!rs.IsEOF() && row--)
1.27 misha 344: rs.MoveNext();
345: }
346: }
1.1 parser 347:
348: unsigned long row=0;
1.10 paf 349: CDBVariant v;
350: CString s;
1.27 misha 351: while(!rs.IsEOF() && (limit==SQL_NO_LIMIT || row<limit)){
352: CHECK(handlers.add_row(sql_error));
353: for(int i=0; i<column_count; i++){
354: size_t length;
355: char* str;
356: switch(column_types[i]){
1.7 paf 357: //case xBOOL:
1.14 paf 358: //case SQL_DATETIME: << default: handles that more properly (?)
359: case SQL_BINARY:
1.11 paf 360: case SQL_VARBINARY:
361: case SQL_LONGVARBINARY:
1.7 paf 362: case SQL_SMALLDATETIME:
1.27 misha 363: //case SQL_NVARCHAR: // mfc 7.1 has errors with nvarchar(length): SQLGetData in dbcore.cpp truncates last byte for unknown reason.
364: // could be fixed by uncommenting this and handing DBVT_WSTRING inside, but it's UNICODE
1.10 paf 365: rs.GetFieldValue(i, v);
1.20 paf 366: getFromDBVariant(services, v, str, length);
1.10 paf 367: break;
1.7 paf 368: default:
1.10 paf 369: rs.GetFieldValue(i, s);
1.20 paf 370: getFromString(services, s, str, length);
1.10 paf 371: break;
1.27 misha 372: }
1.20 paf 373:
1.27 misha 374: // transcode cell value from ?ClientCharset to $request:charset
1.30 misha 375: if(length && transcode_column[i]){
1.27 misha 376: services.transcode(str, length,
377: str, length,
1.30 misha 378: client_charset,
379: request_charset);
380: }
1.20 paf 381:
1.27 misha 382: CHECK(handlers.add_row_cell(sql_error, str, length));
1.1 parser 383: }
1.27 misha 384: rs.MoveNext();
385: row++;
1.1 parser 386: }
387:
388: rs.Close();
389: } else {
390: db->ExecuteSQL(statement);
391: }
1.9 paf 392: } CATCH_ALL (e) {
1.1 parser 393: _throw(services, e);
1.9 paf 394: } END_CATCH_ALL
1.32 ! misha 395:
! 396: if(connection.autocommit)
! 397: commit(aconnection);
1.7 paf 398: }
399:
1.27 misha 400: private:
401: void getFromDBVariant(SQL_Driver_services& services, CDBVariant& v, char*& str, size_t& length){
402: switch(v.m_dwType){
1.14 paf 403: case DBVT_BINARY: /* << would cause problems with current String implementation
404: now falling into NULL case, effectively ignoring such columns [not failing]
405: {
1.17 paf 406: if(length=v.m_pbinary->m_dwDataLength) {
407: str=services.malloc_atomic(length+1);
408: memcpy(ptr, ::GlobalLock(v.m_pbinary->m_hData), length);
1.14 paf 409: ::GlobalUnlock(v.m_pbinary->m_hData);
410: } else
411: str=0;
412: break;
413: }*/
1.7 paf 414: case DBVT_NULL: // No union member is valid for access.
1.14 paf 415: str=0;
1.17 paf 416: length=0;
1.7 paf 417: break;
418: /* case DBVT_BOOL:
419: ptr=v.m_boolVal?"1":"0";
1.17 paf 420: length=1;
1.7 paf 421: break;*/
1.32 ! misha 422: /* case DBVT_UCHAR:
1.17 paf 423: length=strlen(ptr=v.m_chVal);
1.7 paf 424: break;
425: case DBVT_SHORT:
426: char buf[MAX_NUMBER];
1.17 paf 427: length=snprintf(HEAPIZE buf, "%d", v.m_iVal);
1.27 misha 428: break;
429: */
1.7 paf 430: /* case DBVT_LONG:
431: {
432: char local_buf[MAX_NUMBER];
1.17 paf 433: length=snprintf(local_buf, MAX_NUMBER, "%ld", v.m_lVal);
434: ptr=services.malloc_atomic(length);
435: memcpy(ptr, local_buf, length);
1.7 paf 436: break;
1.27 misha 437: }
438: */
439: /*
440: case DBVT_SINGLE:
1.7 paf 441: m_fltVal
442: break;
1.27 misha 443: case DBVT_DOUBLE m_dblVal
444: case DBVT_STRING m_pstring
445: */
1.7 paf 446: case DBVT_DATE:
447: {
448: char local_buf[MAX_STRING];
1.17 paf 449: length=snprintf(local_buf, MAX_STRING,
1.7 paf 450: "%04d-%02d-%02d %02d:%02d:%02d.%03d",
451: v.m_pdate->year,
452: v.m_pdate->month,
453: v.m_pdate->day,
454: v.m_pdate->hour,
455: v.m_pdate->minute,
456: v.m_pdate->second,
1.25 paf 457: v.m_pdate->fraction/1000000); // lexical parser of INCOMING literal choked on times like hh:mm:ss.123000000
1.17 paf 458: str=(char*)services.malloc_atomic(length+1);
459: memcpy(str, local_buf, length+1);
1.7 paf 460: break;
461: }
462: default:
463: char msg[MAX_STRING];
464: snprintf(msg, MAX_STRING, "unknown column return variant type (%d)",
465: v.m_dwType);
466: services._throw(msg);
467: }
468: }
469:
1.27 misha 470: void getFromString(SQL_Driver_services& services, CString& s, char*& astr, size_t& length){
471: if(s.IsEmpty()){
1.14 paf 472: astr=0;
1.17 paf 473: length=0;
1.7 paf 474: } else {
475: const char *cstr=LPCTSTR(s);
1.17 paf 476: length=strlen(cstr); //string.GetLength() works wrong with non-string types:
477: astr=(char*)services.malloc_atomic(length+1);
478: memcpy(astr, cstr, length+1);
1.7 paf 479: }
1.1 parser 480: }
481:
1.27 misha 482: void _throw(SQL_Driver_services& services, CException *e){
483: char szCause[MAX_STRING];
484: szCause[0]=0;
1.1 parser 485: e->GetErrorMessage(szCause, MAX_STRING);
486: char msg[MAX_STRING];
487: snprintf(msg, MAX_STRING, "%s: %s",
488: e->GetRuntimeClass()->m_lpszClassName,
489: *szCause?szCause:"unknown");
490: services._throw(msg);
491: }
492:
1.32 ! misha 493: void _throw(Connection& connection, long value){
! 494: char msg[MAX_STRING];
! 495: snprintf(msg, MAX_STRING, "%u", value);
! 496: connection.services->_throw(msg);
! 497: }
! 498:
1.1 parser 499: };
500:
501: extern "C" SQL_Driver *SQL_DRIVER_CREATE() {
502: return new ODBC_Driver();
1.26 paf 503: }