--- sql/odbc/parser3odbc.C 2004/09/13 14:47:43 1.26 +++ sql/odbc/parser3odbc.C 2008/07/04 16:20:02 1.34 @@ -5,7 +5,7 @@ Author: Alexandr Petrosian (http://paf.design.ru) */ -static const char *RCSId="$Id: parser3odbc.C,v 1.26 2004/09/13 14:47:43 paf Exp $"; +static const char *RCSId="$Id: parser3odbc.C,v 1.34 2008/07/04 16:20:02 misha Exp $"; #ifndef _MSC_VER # error compile ISAPI module with MSVC [no urge for now to make it autoconf-ed (PAF)] @@ -44,28 +44,55 @@ static const char *RCSId="$Id: parser3od #ifndef strncasecmp # define strncasecmp _strnicmp #endif +#ifndef strcasecmp +# define strcasecmp _stricmp +#endif -static char *lsplit(char *string, char delim) { - if(string) { - char *v=strchr(string, delim); - if(v) { +static char *lsplit(char *string, char delim){ + if(string){ + if(char* v=strchr(string, delim)){ *v=0; return v+1; } - } - return 0; + } + return 0; +} + +static char *lsplit(char **string_ref, char delim){ + char *result=*string_ref; + char *next=lsplit(*string_ref, delim); + *string_ref=next; + return result; } -static void toupper_str(char *out, const char *in, size_t size) { +static void toupper_str(char *out, const char *in, size_t size){ while(size--) *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* cstrClientCharset; + const char* client_charset; + SQL::SQLEnum sql_specific; + bool autocommit; }; /** @@ -79,34 +106,59 @@ public: /// get api version int api_version() { return SQL_DRIVER_API_VERSION; } + const char *initialize(char *dlopen_file_spec) { return 0; } /** connect @param url - format: @b DSN=dsn;UID=user;PWD=password (ODBC connect string) + format: @b DSN=dsn;UID=user;PWD=password? (ODBC connect string) + ClientCharset=charset& // transcode with parser + autocommit=1& // 0 -- disable auto commit + FastOffsetSearch=0 WARNING: must be used only to connect, for buffer doesn't live long */ + void connect( - char *url, - SQL_Driver_services& services, - void **connection_ref ///< output: Connection* - ) { - Connection& connection=*(Connection *)services.malloc(sizeof(Connection)); + char *url, + SQL_Driver_services& services, + void **connection_ref ///< output: Connection* + ){ + Connection& connection=*(Connection *)services.malloc(sizeof(Connection)); *connection_ref=&connection; connection.services=&services; - connection.cstrClientCharset=0; - - if(const char* key_start=strstr(url, "ClientCharset=")) { - const char* value_start=key_start+14/*strlen("ClientCharset=")*/; - const char* value_end=strchr(value_start, ';'); - if(!value_end) - value_end=url+strlen(url); - - if(size_t ClientCharsetLength=value_end-value_start) { - char* cstrClientCharset=(char*)services.malloc_atomic(ClientCharsetLength+1); - toupper_str(cstrClientCharset, value_start, ClientCharsetLength); - cstrClientCharset[ClientCharsetLength]=0; - - connection.cstrClientCharset=cstrClientCharset; + connection.client_charset=0; + 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){ + if(char *value=lsplit(key, '=')){ + if(strcmp(key, "ClientCharset")==0){ + toupper_str(value, value, strlen(value)); + connection.client_charset=value; + } else if(strcasecmp(key, "autocommit")==0){ + if(atoi(value)==0) + connection.autocommit=false; + } 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 + services._throw("connect option without =value" /*key*/); + } } } @@ -120,7 +172,8 @@ public: } END_CATCH_ALL } - void disconnect(void *aconnection) { + + void disconnect(void *aconnection){ Connection& connection=*static_cast(aconnection); TRY delete connection.db; @@ -130,7 +183,8 @@ public: } END_CATCH_ALL } - void commit(void *aconnection) { + + void commit(void *aconnection){ Connection& connection=*static_cast(aconnection); TRY connection.db->CommitTrans(); @@ -140,7 +194,8 @@ public: } END_CATCH_ALL } - void rollback(void *aconnection) { + + void rollback(void *aconnection){ Connection& connection=*static_cast(aconnection); TRY connection.db->Rollback(); @@ -151,15 +206,15 @@ public: END_CATCH_ALL } - bool ping(void *connection) { + bool ping(void *connection){ return true; } - const char* quote(void *aconnection, const char *from, unsigned int length) { + const char* quote(void *aconnection, const char *from, unsigned int length){ Connection& connection=*static_cast(aconnection); char *result=(char*)connection.services->malloc_atomic(length*2+1); char *to=result; - while(length--) { + while(length--){ if(*from=='\'') { // ' -> '' *to++='\''; } @@ -168,12 +223,15 @@ public: *to=0; return result; } - void query(void *aconnection, - const char *statement, - size_t placeholders_count, Placeholder* placeholders, - unsigned long offset, unsigned long limit, - SQL_Driver_query_event_handlers& handlers) { + void query(void *aconnection, + const char *astatement, + size_t placeholders_count, + Placeholder* placeholders, + unsigned long offset, + unsigned long limit, + SQL_Driver_query_event_handlers& handlers + ){ Connection& connection=*static_cast(aconnection); CDatabase *db=connection.db; SQL_Driver_services& services=*connection.services; @@ -181,20 +239,27 @@ public: if(placeholders_count>0) services._throw("bind variables not supported (yet)"); - // transcode from $request:charset to connect-string?client_charset - if(const char* cstrClientCharset=connection.cstrClientCharset) { - size_t transcoded_statement_size; - services.transcode(statement, strlen(statement), - statement, transcoded_statement_size, - services.request_charset(), - cstrClientCharset); + 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); + if(transcode_needed){ + // transcode query from $request:charset to ?ClientCharset + size_t length=strlen(statement); + services.transcode(statement, length, + statement, length, + request_charset, + 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 // Server cursors are created only for statements that begin with: // SELECT // EXEC[ute] procedure_name @@ -202,18 +267,20 @@ 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); + || strncasecmp(statement, "{", 1)==0 + ){ + CRecordset rs(db); + DWORD options=CRecordset::executeDirect|CRecordset::readOnly; TRY { rs.Open( - CRecordset::forwardOnly - ||CRecordset::readOnly, + CRecordset::forwardOnly, statement, - CRecordset::executeDirect - ); + options + ); } CATCH_ALL (e) { // could not fetch a table TRY { @@ -231,10 +298,12 @@ public: if(!column_count) services._throw("result contains no columns"); - SWORD column_types[MAX_COLS]; if(column_count>MAX_COLS) column_count=MAX_COLS; + SWORD column_types[MAX_COLS]; + bool transcode_column[MAX_COLS]; + SQL_Error sql_error; #define CHECK(afailed) if(afailed) services._throw(sql_error) @@ -243,43 +312,67 @@ public: CODBCFieldInfo fieldinfo; rs.GetODBCFieldInfo(i, fieldinfo); column_types[i]=fieldinfo.m_nSQLType; + switch(fieldinfo.m_nSQLType){ + case SQL_NUMERIC: + case SQL_DECIMAL: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_FLOAT: + case SQL_REAL: + case SQL_DOUBLE: + case SQL_DATETIME: + case SQL_SMALLDATETIME: + case SQL_BIGINT: + case SQL_TINYINT: + transcode_column[i]=false; + break; + default: + transcode_column[i]=transcode_needed; + break; + } size_t length=fieldinfo.m_strName.GetLength(); char *str=0; - if(length) { + if(length){ str=(char*)services.malloc_atomic(length+1); - memcpy(str, (char *)LPCTSTR(fieldinfo.m_strName), length+1); + memcpy(str, (char*)LPCTSTR(fieldinfo.m_strName), length+1); - // transcode to $request:charset from connect-string?client_charset - if(const char* cstrClientCharset=connection.cstrClientCharset) { + // transcode column name from ?ClientCharset to $request:charset + if(transcode_needed){ services.transcode(str, length, str, length, - cstrClientCharset, - services.request_charset()); + client_charset, + request_charset); } } CHECK(handlers.add_column(sql_error, str, length)); } CHECK(handlers.before_rows(sql_error)); + + // skip offset rows + if(offset && !mstatement.offset){ + unsigned long row=offset; + while(!rs.IsEOF() && row--) + rs.MoveNext(); + } unsigned long row=0; CDBVariant v; CString s; - while(!rs.IsEOF() && (!limit||(row=offset) { - CHECK(handlers.add_row(sql_error)); - for(int i=0; imalloc_atomic( + strlen(astatement) + +MAX_NUMBER + +5/* TOP */ + +1/*terminator*/ + ); + + result.limit=true; // with TOP we can't skip offset records easily + result.statement=statement_limited; + + snprintf(statement_limited, MAX_NUMBER+11, "SELECT TOP %u", (limit)?limit+offset:0/*no reasons to skip something 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; e->GetErrorMessage(szCause, MAX_STRING); char msg[MAX_STRING]; snprintf(msg, MAX_STRING, "%s: %s", @@ -398,6 +565,12 @@ public: services._throw(msg); } + void _throw(Connection& connection, long value){ + char msg[MAX_STRING]; + snprintf(msg, MAX_STRING, "%u", value); + connection.services->_throw(msg); + } + }; extern "C" SQL_Driver *SQL_DRIVER_CREATE() {