--- sql/odbc/parser3odbc.C 2008/07/01 08:51:16 1.30 +++ sql/odbc/parser3odbc.C 2012/04/18 09:39:15 1.39 @@ -1,11 +1,10 @@ /** @file Parser ODBC driver. - Copyright(c) 2001, 2003 ArtLebedev Group (http://www.artlebedev.com) + Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com) Author: Alexandr Petrosian (http://paf.design.ru) */ -static const char *RCSId="$Id: parser3odbc.C,v 1.30 2008/07/01 08:51:16 misha Exp $"; #ifndef _MSC_VER # error compile ISAPI module with MSVC [no urge for now to make it autoconf-ed (PAF)] @@ -21,6 +20,8 @@ static const char *RCSId="$Id: parser3od #define WINVER 0x0400 #include +volatile const char * IDENT_PARSER3ODBC_C="$Id: parser3odbc.C,v 1.39 2012/04/18 09:39:15 moko Exp $" IDENT_PA_SQL_DRIVER_H; + // defines #define MAX_COLS 500 @@ -70,13 +71,29 @@ static void toupper_str(char *out, const *out++=(char)toupper(*in++); } +struct modified_statement { + const char* statement; + bool limit; + bool offset; +}; + +// todo: MySQL, SQLite, PgSQL (add LIMIT at the end of statement) +struct SQL { + enum SQLEnum { + Undefined, + MSSQL, + Pervasive, + FireBird + }; +}; + struct Connection { SQL_Driver_services* services; CDatabase* db; const char* client_charset; + SQL::SQLEnum sql_specific; bool autocommit; - bool fast_offset_search; }; /** @@ -110,12 +127,14 @@ public: *connection_ref=&connection; connection.services=&services; connection.client_charset=0; - connection.fast_offset_search=false; + connection.sql_specific=SQL::Undefined; connection.autocommit=true; size_t url_length=strlen(url); char *options=lsplit(url, '?'); + // todo: analize connect string and if 'SQL Server' found, modify query and add TOP into SELECTs + while(options){ if(char *key=lsplit(&options, '&')){ if(*key){ @@ -126,9 +145,16 @@ public: } else if(strcasecmp(key, "autocommit")==0){ if(atoi(value)==0) connection.autocommit=false; - } else if(strcmp(key, "FastOffsetSearch")==0){ - if(atoi(value)==1) - connection.fast_offset_search=true; + } else if(strcmp(key, "SQL")==0){ + if(strcasecmp(value, "MSSQL")==0){ + connection.sql_specific=SQL::MSSQL; + } else if(strcasecmp(value, "Pervasive")==0){ + connection.sql_specific=SQL::Pervasive; + } else if(strcasecmp(value, "FireBird")==0){ + connection.sql_specific=SQL::FireBird; + } else { + services._throw("unknown value of SQL option was specified" /*key*/); + } } else services._throw("unknown connect option" /*key*/); } else @@ -140,9 +166,7 @@ public: TRY { connection.db=new CDatabase(); connection.db->OpenEx(url, CDatabase::noOdbcDialog); - - if(!connection.autocommit) - connection.db->BeginTrans(); + connection.db->BeginTrans(); } CATCH_ALL (e) { _throw(services, e); @@ -163,50 +187,63 @@ public: void commit(void *aconnection){ Connection& connection=*static_cast(aconnection); - if(!connection.autocommit){ - TRY - connection.db->CommitTrans(); - connection.db->BeginTrans(); - CATCH_ALL (e) { - _throw(*connection.services, e); - } - END_CATCH_ALL + TRY + connection.db->CommitTrans(); + connection.db->BeginTrans(); + CATCH_ALL (e) { + _throw(*connection.services, e); } + END_CATCH_ALL } void rollback(void *aconnection){ Connection& connection=*static_cast(aconnection); - if(!connection.autocommit){ - TRY - connection.db->Rollback(); - connection.db->BeginTrans(); - CATCH_ALL (e) { - _throw(*connection.services, e); - } - END_CATCH_ALL + TRY + connection.db->Rollback(); + connection.db->BeginTrans(); + CATCH_ALL (e) { + _throw(*connection.services, e); } + END_CATCH_ALL } bool ping(void *connection){ return true; } - const char* quote(void *aconnection, const char *from, unsigned int length){ + // charset here is services.request_charset(), not connection.client_charset + // thus we can't use the sql server quoting support + const char* quote(void *aconnection, const char *str, unsigned int length) + { + const char* from; + const char* from_end=str+length; + + size_t quoted=0; + + for(from=str; from(aconnection); - char *result=(char*)connection.services->malloc_atomic(length*2+1); - char *to=result; - while(length--){ - if(*from=='\'') { // ' -> '' - *to++='\''; - } - *to++=*from++; + char *result=(char*)connection.services->malloc_atomic(length + quoted + 1); + char *to = result; + + for(from=str; from '' + *to++=*from; } + *to=0; return result; } void query(void *aconnection, - const char *statement, + const char *astatement, size_t placeholders_count, Placeholder* placeholders, unsigned long offset, @@ -220,12 +257,17 @@ public: if(placeholders_count>0) services._throw("bind variables not supported (yet)"); + while(isspace((unsigned char)*astatement)) + astatement++; + + modified_statement mstatement=_preprocess_statement(connection, astatement, offset, limit); + const char* statement=mstatement.statement; + const char* client_charset=connection.client_charset; const char* request_charset=services.request_charset(); bool transcode_needed=(client_charset && strcmp(client_charset, request_charset)!=0); - - // transcode query from $request:charset to ?ClientCharset if(transcode_needed){ + // transcode query from $request:charset to ?ClientCharset size_t length=strlen(statement); services.transcode(statement, length, statement, length, @@ -233,9 +275,6 @@ public: client_charset); } - while(isspace((unsigned char)*statement)) - statement++; - TRY { // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\adosql.chm::/adoprg02_4g33.htm // or http://msdn.microsoft.com/en-us/library/aa905899(SQL.80).aspx @@ -246,27 +285,17 @@ public: // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\odbcsql.chm::/od_6_035_5dnp.htm // The ODBC CALL escape sequence for calling a procedure is: // {[?=]call procedure_name[([parameter][,[parameter]]...)]} - if(strncasecmp(statement, "SELECT", 6)==0 + if( + strncasecmp(statement, "SELECT", 6)==0 || strncasecmp(statement, "EXEC", 4)==0 || strncasecmp(statement, "call", 4)==0 || strncasecmp(statement, "{", 1)==0 ){ CRecordset rs(db); DWORD options=CRecordset::executeDirect|CRecordset::readOnly; - //CRecordset::skipDeletedRecords - //CRecordset::useMultiRowFetch - //CRecordset::userAllocMultiRowBuffers - //CRecordset::useExtendedFetch -/* - if(connection.fast_offset_search){ - options+=CRecordset::useMultiRowFetch+CRecordset::userAllocMultiRowBuffers; - } else { - options+=CRecordset::skipDeletedRecords; - } -*/ TRY { rs.Open( - (connection.fast_offset_search)?CRecordset::dynamic:CRecordset::forwardOnly, + CRecordset::forwardOnly, statement, options ); @@ -317,7 +346,7 @@ public: break; default: transcode_column[i]=transcode_needed; - + break; } size_t length=fieldinfo.m_strName.GetLength(); char *str=0; @@ -339,16 +368,10 @@ public: CHECK(handlers.before_rows(sql_error)); // skip offset rows - if(offset){ - if(connection.fast_offset_search){ - rs.Move(offset); - } else { - unsigned long row=offset; - while(!rs.IsEOF() && row>0){ - rs.MoveNext(); - row--; - } - } + if(offset && limit!=0 && !mstatement.offset){ + unsigned long row=offset; + while(!rs.IsEOF() && row--) + rs.MoveNext(); } unsigned long row=0; @@ -398,6 +421,9 @@ public: } CATCH_ALL (e) { _throw(services, e); } END_CATCH_ALL + + if(connection.autocommit) + commit(aconnection); } private: @@ -422,7 +448,7 @@ private: ptr=v.m_boolVal?"1":"0"; length=1; break;*/ -/* case DBVT_UCHAR: +/* case DBVT_UCHAR: length=strlen(ptr=v.m_chVal); break; case DBVT_SHORT: @@ -482,6 +508,70 @@ private: } } + modified_statement _preprocess_statement( + Connection& connection, + const char* astatement, + unsigned long offset, + unsigned long limit + ){ + modified_statement result={astatement, false, false}; + + if(limit!=SQL_NO_LIMIT && connection.sql_specific!=SQL::Undefined && strncasecmp(astatement, "select", 6)==0){ + switch(connection.sql_specific){ + case SQL::MSSQL: + case SQL::Pervasive: // uses TOP as well + { + // add ' TOP limit+offset' after 'SELECT' + char* statement_limited=(char *)connection.services->malloc_atomic( + strlen(astatement) + +MAX_NUMBER + +5/* TOP */ + +1/*terminator*/ + ); + + result.limit=true; + result.statement=statement_limited; + + snprintf(statement_limited, MAX_NUMBER+11, "SELECT TOP %u", (limit)?limit+offset:0/*no point to skip anything if we need 0 rows*/); + + astatement+=6;/*skip 'select'*/ + strcat(statement_limited, astatement); + + //connection.services->_throw(result.statement); + break; + } + case SQL::FireBird: + { + // add ' FIRST (limit) SKIP (offset)' after 'SELECT' + char* statement_limited=(char *)connection.services->malloc_atomic( + strlen(astatement) + +MAX_NUMBER*2 + +9/* FIRST ()*/ + +offset?8:0/* SKIP ()*/ + +1/*terminator*/ + ); + + result.limit=true; + result.offset=true; + result.statement=statement_limited; + + statement_limited+=snprintf(statement_limited, MAX_NUMBER+15, "SELECT FIRST (%u)", limit); + if(offset && limit/*no reasons to skip something if we need 0 rows*/) + statement_limited+=snprintf(statement_limited, MAX_NUMBER+8, " SKIP (%u)", offset); + + astatement+=6;/*skip 'select'*/ + strcat((char*)result.statement, astatement); + + //connection.services->_throw(result.statement); + break; + } + default: + connection.services->_throw("Unknown SQL specifics"); + } + } + return result; + } + void _throw(SQL_Driver_services& services, CException *e){ char szCause[MAX_STRING]; szCause[0]=0;