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