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