Diff for /sql/odbc/parser3odbc.C between versions 1.4 and 1.38

version 1.4, 2002/02/08 07:28:14 version 1.38, 2012/03/16 10:05:04
Line 1 Line 1
 /** @file  /** @file
         Parser ODBC driver.          Parser ODBC driver.
   
         Copyright(c) 2001, 2002 ArtLebedev Group(http://www.artlebedev.com)          Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com)
   
         Author: Alexander Petrosyan <paf@design.ru>(http://design.ru/paf)          Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
 */  */
 static const char *RCSId="$Id$";   
   
 #ifndef _MSC_VER  #ifndef _MSC_VER
 #       error compile ISAPI module with MSVC [no urge for now to make it autoconf-ed (PAF)]  #       error compile ISAPI module with MSVC [no urge for now to make it autoconf-ed (PAF)]
Line 18  static const char *RCSId="$Id$"; Line 17  static const char *RCSId="$Id$";
   
 #include "pa_sql_driver.h"  #include "pa_sql_driver.h"
   
   #define WINVER 0x0400
 #include <AFXDB.H>  #include <AFXDB.H>
   
   volatile const char * IDENT_PARSER3ODBC_C=="$Id$" IDENT_PA_SQL_DRIVER_H;
   
   // defines
   
   #define MAX_COLS 500
   
 #define MAX_STRING 0x400  #define MAX_STRING 0x400
   #define MAX_NUMBER 40
   
   // new in MSSQL2000, no MFC constants
   #ifndef SQL_NVARCHAR
   #define SQL_NVARCHAR (-9)
   #endif
   #ifndef SQL_NTEXT 
   #define SQL_NTEXT (-10)
   #endif
   #ifndef SQL_SMALLDATETIME
   #define SQL_SMALLDATETIME 11
   #endif
   // create table test (id int, a smalldatetime, b ntext, c nvarchar(100))
   
 #define snprintf _snprintf  #define snprintf _snprintf
 #ifndef strncasecmp  #ifndef strncasecmp
 #       define strncasecmp _strnicmp  #       define strncasecmp _strnicmp
 #endif  #endif
   #ifndef strcasecmp
   #       define strcasecmp _stricmp
   #endif
   
 static char *lsplit(char *string, char delim) {  static char *lsplit(char *string, char delim){
     if(string) {          if(string){
                 char *v=strchr(string, delim);                  if(char* v=strchr(string, delim)){
                 if(v) {  
                         *v=0;                          *v=0;
                         return v+1;                          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){
           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* client_charset;
           SQL::SQLEnum sql_specific;
           bool autocommit;
   };
   
 /**  /**
         ODBC server driver          ODBC server driver
 */  */
Line 49  public: Line 107  public:
   
         /// get api version          /// get api version
         int api_version() { return SQL_DRIVER_API_VERSION; }          int api_version() { return SQL_DRIVER_API_VERSION; }
   
         const char *initialize(char *dlopen_file_spec) { return 0; }          const char *initialize(char *dlopen_file_spec) { return 0; }
         /**     connect          /**     connect
                 @param used_only_in_connect_url                  @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                          WARNING: must be used only to connect, for buffer doesn't live long
         */          */
   
         void connect(          void connect(
                 char *used_only_in_connect_url,                           char *url, 
                 SQL_Driver_services& services,                           SQL_Driver_services& services, 
                 void **connection ///< output: CDatabase *                          void **connection_ref ///< output: Connection*
                 ) {          ){
         //      _asm int 3;                  Connection& connection=*(Connection *)services.malloc(sizeof(Connection));
                 CDatabase *db;                  *connection_ref=&connection;
                   connection.services=&services;
                   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*/);
                                   }
                           }
                   }
   
                 TRY {                  TRY {
                         db=new CDatabase();                          connection.db=new CDatabase();
                         db->OpenEx(used_only_in_connect_url, CDatabase::noOdbcDialog);                          connection.db->OpenEx(url, CDatabase::noOdbcDialog);
                         db->BeginTrans();                          connection.db->BeginTrans();
                 }                   } 
                 CATCH_ALL (e) {                  CATCH_ALL (e) {
                         _throw(services, e);                          _throw(services, e);
                         db=0; // calm, compiler  
                 }                  }
                 END_CATCH_ALL                  END_CATCH_ALL
   
                 *(CDatabase **)connection=db;  
         }          }
         void disconnect(void *connection) {  
                 CDatabase *db=static_cast<CDatabase *>(connection);          void disconnect(void *aconnection){
                   Connection& connection=*static_cast<Connection*>(aconnection);
                 TRY                  TRY
                         delete db;                          delete connection.db;
                           connection.db=0;
                 CATCH_ALL (e) {                  CATCH_ALL (e) {
                         // nothing                          // nothing
                 }                  }
                 END_CATCH_ALL                  END_CATCH_ALL
         }          }
         void commit(SQL_Driver_services& services, void *connection) {  
                 CDatabase *db=static_cast<CDatabase *>(connection);          void commit(void *aconnection){
                   Connection& connection=*static_cast<Connection*>(aconnection);
                 TRY                  TRY
                         db->CommitTrans();                          connection.db->CommitTrans();
                         db->BeginTrans();                          connection.db->BeginTrans();
                 CATCH_ALL (e) {                  CATCH_ALL (e) {
                         _throw(services, e);                          _throw(*connection.services, e);
                 }                  }
                 END_CATCH_ALL                  END_CATCH_ALL
         }          }
         void rollback(SQL_Driver_services& services, void *connection) {  
                 CDatabase *db=static_cast<CDatabase *>(connection);          void rollback(void *aconnection){
                   Connection& connection=*static_cast<Connection*>(aconnection);
                 TRY                  TRY
                         db->Rollback();                          connection.db->Rollback();
                         db->BeginTrans();                          connection.db->BeginTrans();
                 CATCH_ALL (e) {                  CATCH_ALL (e) {
                         _throw(services, e);                          _throw(*connection.services, e);
                 }                  }
                 END_CATCH_ALL                  END_CATCH_ALL
         }          }
   
         bool ping(SQL_Driver_services&, void *connection) {          bool ping(void *connection){
                 return true;                  return true;
         }          }
   
         unsigned int quote(          // charset here is services.request_charset(), not connection.client_charset
                 SQL_Driver_services&, void *connection,          // thus we can't use the sql server quoting support
                 char *to, const char *from, unsigned int length) {          const char* quote(void *aconnection, const char *str, unsigned int length) 
                 if(to) { // store mode          {
                         unsigned int result=length;                  const char* from;
                         while(length--) {                  const char* from_end=str+length;
                                 if(*from=='\'') { // ' -> ''  
                                         *to++='\''; result++;                  size_t quoted=0;
                                 }  
                                 *to++=*from++;                  for(from=str; from<from_end; from++){
                         }                          if(*from=='\'')
                         return result;                                  quoted++;
                 } else // estimate mode                  }
                         return length*2;  
         }  
         void query(  
                 SQL_Driver_services& services, void *connection,   
                 const char *statement, unsigned long offset, unsigned long limit,  
                 SQL_Driver_query_event_handlers& handlers) {  
   
                 CDatabase *db=static_cast<CDatabase *>(connection);                  if(!quoted)
                           return str;
   
                 while(isspace(*statement))                   Connection& connection=*static_cast<Connection*>(aconnection);
                         statement++;                  char *result=(char*)connection.services->malloc_atomic(length + quoted + 1);
                   char *to = result;
   
                   for(from=str; from<from_end; from++){
                           if(*from=='\'')
                                   *to++= '\''; // ' -> ''
                           *to++=*from;
                   }
                                   
                   *to=0;
                   return result;
           }
   
           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<Connection*>(aconnection);
                   CDatabase *db=connection.db;
                   SQL_Driver_services& services=*connection.services;
   
                   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);
                   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);
                   }
   
                 TRY {                  TRY {
                         if(strncasecmp(statement, "select", 6)==0) {                          // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\adosql.chm::/adoprg02_4g33.htm
                                 CRecordset rs(db);                           // or http://msdn.microsoft.com/en-us/library/aa905899(SQL.80).aspx
                                 rs.Open(                          // Server cursors are created only for statements that begin with: 
                                         CRecordset::forwardOnly,                           // SELECT
                                         statement,                          // EXEC[ute] procedure_name
                                         CRecordset::executeDirect                             // call procedure_name
                           // 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
                                   || strncasecmp(statement, "EXEC", 4)==0
                                   || strncasecmp(statement, "call", 4)==0
                                   || strncasecmp(statement, "{", 1)==0
                           ){
                                   CRecordset rs(db);
                                   DWORD options=CRecordset::executeDirect|CRecordset::readOnly;
                                   TRY {
                                           rs.Open(
                                                   CRecordset::forwardOnly,
                                                   statement,
                                                   options
                                         );                                          );
                                   } CATCH_ALL (e) {
                                           // could not fetch a table
                                           TRY {
                                                   // then try resultless query
                                                   db->ExecuteSQL(statement);
                                                   // OK then
                                                   return;
                                           } CATCH_ALL (e2) {
                                                   // still nothing good
                                                   _throw(services, e); // throw ORIGINAL exception
                                           } END_CATCH_ALL
                                   } END_CATCH_ALL
   
                                 int column_count=rs.GetODBCFieldCount();                                  int column_count=rs.GetODBCFieldCount();
                                 if(!column_count)                                  if(!column_count)
                                         services._throw("result contains no columns");                                          services._throw("result contains no columns");
   
                                   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)
   
                                 for(int i=0; i<column_count; i++){                                  for(int i=0; i<column_count; i++){
                                         CString string;                                          CString string;
                                         CODBCFieldInfo fieldinfo;                                          CODBCFieldInfo fieldinfo;
                                         rs.GetODBCFieldInfo(i, fieldinfo);                                          rs.GetODBCFieldInfo(i, fieldinfo);
                                         size_t size=fieldinfo.m_strName.GetLength();                                          column_types[i]=fieldinfo.m_nSQLType;
                                         void *ptr=0;                                          switch(fieldinfo.m_nSQLType){
                                         if(size) {                                                  case SQL_NUMERIC:
                                                 ptr=services.malloc(size);                                                  case SQL_DECIMAL:
                                                 memcpy(ptr, (char *)LPCTSTR(fieldinfo.m_strName), size);                                                  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){
                                                   str=(char*)services.malloc_atomic(length+1);
                                                   memcpy(str, (char*)LPCTSTR(fieldinfo.m_strName), length+1);
   
                                                   // transcode column name from ?ClientCharset to $request:charset
                                                   if(transcode_needed){
                                                           services.transcode(str, length,
                                                                   str, length,
                                                                   client_charset,
                                                                   request_charset);
                                                   }
                                         }                                          }
                                         handlers.add_column(ptr, size);                                          CHECK(handlers.add_column(sql_error, str, length));
                                 }                                  }
   
                                 handlers.before_rows();                                  CHECK(handlers.before_rows(sql_error));
                                   
                                   // skip offset rows
                                   if(offset && limit!=0 && !mstatement.offset){
                                           unsigned long row=offset;
                                           while(!rs.IsEOF() && row--)
                                                   rs.MoveNext();
                                   }
   
                                 unsigned long row=0;                                  unsigned long row=0;
                                 while(!rs.IsEOF() && (!limit||(row<offset+limit))) {                                  CDBVariant v;
                                         if(row>=offset) {                                  CString s;
                                                 handlers.add_row();                                  while(!rs.IsEOF() && (limit==SQL_NO_LIMIT || row<limit)){
                                                 for(int i=0; i<column_count; i++)                                               {                                          CHECK(handlers.add_row(sql_error));
                                                         CString string;                                          for(int i=0; i<column_count; i++){
                                                         rs.GetFieldValue(i, string);                                                  size_t length;
                                                         size_t size=string.GetLength();                                                  char* str;
                                                         void *ptr=0;                                                  switch(column_types[i]){
                                                         if(size) {                                                          //case xBOOL:
                                                                 ptr=services.malloc(size);                                                          //case SQL_DATETIME: << default: handles that more properly (?)
                                                                 memcpy(ptr, (char *)LPCTSTR(string), size);                                                          case SQL_BINARY: 
                                                         }                                                          case SQL_VARBINARY:
                                                         handlers.add_row_cell(ptr, size);                                                          case SQL_LONGVARBINARY:
                                                           case SQL_SMALLDATETIME:
                                                           //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
                                                                   rs.GetFieldValue(i, v);
                                                                   getFromDBVariant(services, v, str, length);
                                                                   break;
                                                           default:
                                                                   rs.GetFieldValue(i, s);
                                                                   getFromString(services, s, str, length);
                                                                   break;
                                                   }
   
                                                   // transcode cell value from ?ClientCharset to $request:charset
                                                   if(length && transcode_column[i]){
                                                           services.transcode(str, length,
                                                                   str, length,
                                                                   client_charset,
                                                                   request_charset);
                                                 }                                                  }
   
                                                   CHECK(handlers.add_row_cell(sql_error, str, length));
                                         }                                          }
                                         rs.MoveNext();  row++;                                          rs.MoveNext();
                                           row++;
                                 }                                  }
                                                                   
                                 rs.Close();                                  rs.Close();
                         } else {                          } else {
                                 db->ExecuteSQL(statement);                                  db->ExecuteSQL(statement);
                         }                          }
                 }                   } CATCH_ALL (e) {
                 CATCH_ALL (e) {  
                         _throw(services, e);                          _throw(services, e);
                   } END_CATCH_ALL
   
                   if(connection.autocommit)
                           commit(aconnection);
           }
   
   private:
           void getFromDBVariant(SQL_Driver_services& services, CDBVariant& v, char*& str, size_t& length){
                   switch(v.m_dwType){
                   case DBVT_BINARY: /* << would cause problems with current String implementation
                                     now falling into NULL case, effectively ignoring such columns [not failing]
                           {
                                   if(length=v.m_pbinary->m_dwDataLength) {
                                           str=services.malloc_atomic(length+1);
                                           memcpy(ptr, ::GlobalLock(v.m_pbinary->m_hData), length);
                                           ::GlobalUnlock(v.m_pbinary->m_hData);
                                   } else 
                                           str=0;
                                   break;
                           }*/ 
                   case DBVT_NULL: // No union member is valid for access. 
                           str=0;
                           length=0;
                           break;
   /*              case DBVT_BOOL:
                           ptr=v.m_boolVal?"1":"0";
                           length=1;
                           break;*/
   /*              case DBVT_UCHAR:
                           length=strlen(ptr=v.m_chVal);
                           break;
                   case DBVT_SHORT:
                           char buf[MAX_NUMBER];
                           length=snprintf(HEAPIZE buf, "%d", v.m_iVal);
                           break;
   */
   /*              case DBVT_LONG: 
                           {
                                   char local_buf[MAX_NUMBER];
                                   length=snprintf(local_buf, MAX_NUMBER, "%ld", v.m_lVal);
                                   ptr=services.malloc_atomic(length);
                                   memcpy(ptr, local_buf, length);
                                   break;
                           }
   */
   /*
                   case DBVT_SINGLE:
                           m_fltVal 
                           break;
                   case DBVT_DOUBLE m_dblVal 
                   case DBVT_STRING m_pstring
   */ 
                   case DBVT_DATE:
                           {
                                   char local_buf[MAX_STRING];
                                   length=snprintf(local_buf, MAX_STRING, 
                                           "%04d-%02d-%02d %02d:%02d:%02d.%03d",
                                           v.m_pdate->year, 
                                           v.m_pdate->month,
                                           v.m_pdate->day,
                                           v.m_pdate->hour,
                                           v.m_pdate->minute,
                                           v.m_pdate->second,
                                           v.m_pdate->fraction/1000000); // lexical parser of INCOMING literal choked on times like hh:mm:ss.123000000
                                   str=(char*)services.malloc_atomic(length+1);
                                   memcpy(str, local_buf, length+1);
                                   break;
                           }
                   default:
                           char msg[MAX_STRING];
                           snprintf(msg, MAX_STRING, "unknown column return variant type (%d)",
                                   v.m_dwType);
                           services._throw(msg);
                 }                  }
                 END_CATCH_ALL  
         }          }
   
         void _throw(SQL_Driver_services& services, CException *e) {          void getFromString(SQL_Driver_services& services, CString& s, char*& astr, size_t& length){
                 char szCause[MAX_STRING]; szCause[0]=0;                  if(s.IsEmpty()){
                           astr=0;
                           length=0;
                   } else {
                           const char *cstr=LPCTSTR(s);
                           length=strlen(cstr); //string.GetLength() works wrong with non-string types: 
                           astr=(char*)services.malloc_atomic(length+1);
                           memcpy(astr, cstr, length+1);
                   }
           }
   
           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;
                 e->GetErrorMessage(szCause, MAX_STRING);                  e->GetErrorMessage(szCause, MAX_STRING);
                 char msg[MAX_STRING];                  char msg[MAX_STRING];
                 snprintf(msg, MAX_STRING, "%s: %s",                  snprintf(msg, MAX_STRING, "%s: %s",
Line 202  public: Line 583  public:
                 services._throw(msg);                  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() {  extern "C" SQL_Driver *SQL_DRIVER_CREATE() {
         return new ODBC_Driver();          return new ODBC_Driver();
 }  
   
   }

Removed from v.1.4  
changed lines
  Added in v.1.38


E-mail: