Diff for /sql/odbc/parser3odbc.C between versions 1.26.2.1 and 1.33

version 1.26.2.1, 2008/06/30 10:13:04 version 1.33, 2008/07/04 15:36:05
Line 44  static const char *RCSId="$Id$"; Line 44  static const char *RCSId="$Id$";
 #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) {  static void toupper_str(char *out, const char *in, size_t size){
         while(size--)          while(size--)
                 *out++=(char)toupper(*in++);                  *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 {  struct Connection {
         SQL_Driver_services* services;          SQL_Driver_services* services;
   
         CDatabase* db;          CDatabase* db;
         const char* cstrClientCharset;          const char* client_charset;
           SQL::SQLEnum sql_specific;
           bool autocommit;
 };  };
   
 /**  /**
Line 79  public: Line 106  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 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 *url,                           char *url, 
                 SQL_Driver_services& services,                           SQL_Driver_services& services, 
                 void **connection_ref ///< output: Connection*                          void **connection_ref ///< output: Connection*
                 ) {          ){
                 Connection& connection=*(Connection  *)services.malloc(sizeof(Connection));                  Connection& connection=*(Connection *)services.malloc(sizeof(Connection));
                 *connection_ref=&connection;                  *connection_ref=&connection;
                 connection.services=&services;                  connection.services=&services;
                 connection.cstrClientCharset=0;                  connection.client_charset=0;
                   connection.sql_specific=SQL::Undefined;
                 if(const char* key_start=strstr(url, "ClientCharset=")) {                  connection.autocommit=true;
                         const char* value_start=key_start+14/*strlen("ClientCharset=")*/;  
                         const char* value_end=strchr(value_start, ';');                  size_t url_length=strlen(url);
                         if(!value_end)                  char *options=lsplit(url, '?');
                                 value_end=url+strlen(url);  
                   // todo: analize connect string and if 'SQL Server' found, modify query and add TOP into SELECTs
                         if(size_t ClientCharsetLength=value_end-value_start) {  
                                 char* cstrClientCharset=(char*)services.malloc_atomic(ClientCharsetLength+1);                  while(options){
                                 toupper_str(cstrClientCharset, value_start, ClientCharsetLength);                          if(char *key=lsplit(&options, '&')){
                                 cstrClientCharset[ClientCharsetLength]=0;                                  if(*key){
                                           if(char *value=lsplit(key, '=')){
                                 connection.cstrClientCharset=cstrClientCharset;                                                  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*/);
                                   }
                         }                          }
                 }                  }
   
Line 120  public: Line 172  public:
                 }                  }
                 END_CATCH_ALL                  END_CATCH_ALL
         }          }
         void disconnect(void *aconnection) {  
           void disconnect(void *aconnection){
                 Connection& connection=*static_cast<Connection*>(aconnection);                  Connection& connection=*static_cast<Connection*>(aconnection);
                 TRY                  TRY
                         delete connection.db;                          delete connection.db;
Line 130  public: Line 183  public:
                 }                  }
                 END_CATCH_ALL                  END_CATCH_ALL
         }          }
         void commit(void *aconnection) {  
           void commit(void *aconnection){
                 Connection& connection=*static_cast<Connection*>(aconnection);                  Connection& connection=*static_cast<Connection*>(aconnection);
                 TRY                  TRY
                         connection.db->CommitTrans();                          connection.db->CommitTrans();
Line 140  public: Line 194  public:
                 }                  }
                 END_CATCH_ALL                  END_CATCH_ALL
         }          }
         void rollback(void *aconnection) {  
           void rollback(void *aconnection){
                 Connection& connection=*static_cast<Connection*>(aconnection);                  Connection& connection=*static_cast<Connection*>(aconnection);
                 TRY                  TRY
                         connection.db->Rollback();                          connection.db->Rollback();
Line 151  public: Line 206  public:
                 END_CATCH_ALL                  END_CATCH_ALL
         }          }
   
         bool ping(void *connection) {          bool ping(void *connection){
                 return true;                  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<Connection*>(aconnection);                  Connection& connection=*static_cast<Connection*>(aconnection);
                 char *result=(char*)connection.services->malloc_atomic(length*2+1);                  char *result=(char*)connection.services->malloc_atomic(length*2+1);
                 char *to=result;                  char *to=result;
                 while(length--) {                  while(length--){
                         if(*from=='\'') { // ' -> ''                          if(*from=='\'') { // ' -> ''
                                 *to++='\'';                                  *to++='\'';
                         }                          }
Line 168  public: Line 223  public:
                 *to=0;                  *to=0;
                 return result;                  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<Connection*>(aconnection);                  Connection& connection=*static_cast<Connection*>(aconnection);
                 CDatabase *db=connection.db;                  CDatabase *db=connection.db;
                 SQL_Driver_services& services=*connection.services;                  SQL_Driver_services& services=*connection.services;
Line 181  public: Line 239  public:
                 if(placeholders_count>0)                  if(placeholders_count>0)
                         services._throw("bind variables not supported (yet)");                          services._throw("bind variables not supported (yet)");
   
                 // transcode from $request:charset to connect-string?client_charset                  while(isspace((unsigned char)*astatement)) 
                 if(const char* cstrClientCharset=connection.cstrClientCharset) {                          astatement++;
                         size_t transcoded_statement_size;  
                         services.transcode(statement, strlen(statement),                  modified_statement mstatement=_preprocess_statement(connection, astatement, offset, limit);
                                 statement, transcoded_statement_size,                  const char* statement=mstatement.statement;
                                 services.request_charset(),  
                                 cstrClientCharset);                  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 {                  TRY {
                         // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\adosql.chm::/adoprg02_4g33.htm                          // 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:                           // Server cursors are created only for statements that begin with: 
                         // SELECT                          // SELECT
                         // EXEC[ute] procedure_name                          // EXEC[ute] procedure_name
Line 202  public: Line 267  public:
                         // mk:@MSITStore:C:\Program%20Files\Microsoft%20SQL%20Server\80\Tools\Books\odbcsql.chm::/od_6_035_5dnp.htm                          // 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:                          // The ODBC CALL escape sequence for calling a procedure is:
                         // {[?=]call procedure_name[([parameter][,[parameter]]...)]}                          // {[?=]call procedure_name[([parameter][,[parameter]]...)]}
                         if(strncasecmp(statement, "select", 6)==0                          if(
                                   strncasecmp(statement, "SELECT", 6)==0
                                 || strncasecmp(statement, "EXEC", 4)==0                                  || strncasecmp(statement, "EXEC", 4)==0
                                 || strncasecmp(statement, "call", 4)==0                                  || strncasecmp(statement, "call", 4)==0
                                 || strncasecmp(statement, "{", 1)==0) {                                  || strncasecmp(statement, "{", 1)==0
                                 CRecordset rs(db);                           ){
                                   CRecordset rs(db);
                                   DWORD options=CRecordset::executeDirect|CRecordset::readOnly;
                                 TRY {                                  TRY {
                                         rs.Open(                                          rs.Open(
                                                 CRecordset::forwardOnly,                                                  CRecordset::forwardOnly,
                                                 statement,                                                  statement,
                                                 CRecordset::executeDirect|CRecordset::readOnly                                                  options
                                         );                                          );
                                 } CATCH_ALL (e) {                                  } CATCH_ALL (e) {
                                         // could not fetch a table                                          // could not fetch a table
Line 230  public: Line 298  public:
                                 if(!column_count)                                  if(!column_count)
                                         services._throw("result contains no columns");                                          services._throw("result contains no columns");
   
                                 SWORD column_types[MAX_COLS];  
                                 if(column_count>MAX_COLS)                                  if(column_count>MAX_COLS)
                                         column_count=MAX_COLS;                                          column_count=MAX_COLS;
   
                                   SWORD column_types[MAX_COLS];
                                   bool transcode_column[MAX_COLS];
   
                                 SQL_Error sql_error;                                  SQL_Error sql_error;
 #define CHECK(afailed) if(afailed) services._throw(sql_error)  #define CHECK(afailed) if(afailed) services._throw(sql_error)
   
Line 242  public: Line 312  public:
                                         CODBCFieldInfo fieldinfo;                                          CODBCFieldInfo fieldinfo;
                                         rs.GetODBCFieldInfo(i, fieldinfo);                                          rs.GetODBCFieldInfo(i, fieldinfo);
                                         column_types[i]=fieldinfo.m_nSQLType;                                          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();                                          size_t length=fieldinfo.m_strName.GetLength();
                                         char *str=0;                                          char *str=0;
                                         if(length) {                                          if(length){
                                                 str=(char*)services.malloc_atomic(length+1);                                                  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                                                  // transcode column name from ?ClientCharset to $request:charset
                                                 if(const char* cstrClientCharset=connection.cstrClientCharset) {                                                  if(transcode_needed){
                                                         services.transcode(str, length,                                                          services.transcode(str, length,
                                                                 str, length,                                                                  str, length,
                                                                 cstrClientCharset,                                                                  client_charset,
                                                                 services.request_charset());                                                                  request_charset);
                                                 }                                                  }
                                         }                                          }
                                         CHECK(handlers.add_column(sql_error, str, length));                                          CHECK(handlers.add_column(sql_error, str, length));
Line 262  public: Line 350  public:
                                 CHECK(handlers.before_rows(sql_error));                                  CHECK(handlers.before_rows(sql_error));
                                                                   
                                 // skip offset rows                                  // skip offset rows
                                 if(offset){                                  if(offset && !mstatement.offset){
                                         unsigned long row=offset;                                          unsigned long row=offset;
                                         while(!rs.IsEOF() && row>0){                                          while(!rs.IsEOF() && row--)
                                                 rs.MoveNext();                                                  rs.MoveNext();
                                                 row--;  
                                         }  
                                 }                                  }
   
                                 unsigned long row=0;                                  unsigned long row=0;
                                 CDBVariant v;                                  CDBVariant v;
                                 CString s;                                  CString s;
                                 while(!rs.IsEOF() && (!limit || row<limit)) {                                  while(!rs.IsEOF() && (limit==SQL_NO_LIMIT || row<limit)){
                                         CHECK(handlers.add_row(sql_error));                                          CHECK(handlers.add_row(sql_error));
                                         for(int i=0; i<column_count; i++) {                                          for(int i=0; i<column_count; i++){
                                                 size_t length;                                                  size_t length;
                                                 char* str;                                                  char* str;
                                                 switch(column_types[i]) {                                                  switch(column_types[i]){
                                                         //case xBOOL:                                                          //case xBOOL:
                                                         //case SQL_INTEGER: // serg@design.ru did that in parser2. test first!  
                                                         //case SQL_DATETIME: << default: handles that more properly (?)                                                          //case SQL_DATETIME: << default: handles that more properly (?)
                                                         case SQL_BINARY:                                                           case SQL_BINARY: 
                                                         case SQL_VARBINARY:                                                          case SQL_VARBINARY:
                                                         case SQL_LONGVARBINARY:                                                          case SQL_LONGVARBINARY:
                                                         case SQL_SMALLDATETIME:                                                          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                                                          //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);                                                                  rs.GetFieldValue(i, v);
                                                                 getFromDBVariant(services, v, str, length);                                                                  getFromDBVariant(services, v, str, length);
                                                                 break;                                                                  break;
Line 296  public: Line 382  public:
                                                                 break;                                                                  break;
                                                 }                                                  }
   
                                                 // transcode to $request:charset from connect-string?client_charset                                                  // transcode cell value from ?ClientCharset to $request:charset
                                                         if(const char* cstrClientCharset=connection.cstrClientCharset)                                                  if(length && transcode_column[i]){
                                                         services.transcode(str, length,                                                          services.transcode(str, length,
                                                                 str, length,                                                                  str, length,
                                                                 cstrClientCharset,                                                                  client_charset,
                                                                 services.request_charset());                                                                  request_charset);
                                                   }
   
                                                 CHECK(handlers.add_row_cell(sql_error, str, length));                                                  CHECK(handlers.add_row_cell(sql_error, str, length));
                                         }                                          }
Line 316  public: Line 403  public:
                 } CATCH_ALL (e) {                  } CATCH_ALL (e) {
                         _throw(services, e);                          _throw(services, e);
                 } END_CATCH_ALL                  } END_CATCH_ALL
   
                   if(connection.autocommit)
                           commit(aconnection);
         }          }
   
         void getFromDBVariant(SQL_Driver_services& services, CDBVariant& v, char*& str, size_t& length) {  private:
                 switch(v.m_dwType) {          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                  case DBVT_BINARY: /* << would cause problems with current String implementation
                                   now falling into NULL case, effectively ignoring such columns [not failing]                                    now falling into NULL case, effectively ignoring such columns [not failing]
                         {                          {
Line 339  public: Line 430  public:
                         ptr=v.m_boolVal?"1":"0";                          ptr=v.m_boolVal?"1":"0";
                         length=1;                          length=1;
                         break;*/                          break;*/
 /*                                                      case DBVT_UCHAR:  /*              case DBVT_UCHAR:
                         length=strlen(ptr=v.m_chVal);                          length=strlen(ptr=v.m_chVal);
                         break;                          break;
                 case DBVT_SHORT:                  case DBVT_SHORT:
                         char buf[MAX_NUMBER];                          char buf[MAX_NUMBER];
                         length=snprintf(HEAPIZE buf, "%d", v.m_iVal);                          length=snprintf(HEAPIZE buf, "%d", v.m_iVal);
                         break;*/                          break;
   */
 /*              case DBVT_LONG:   /*              case DBVT_LONG: 
                         {                          {
                                 char local_buf[MAX_NUMBER];                                  char local_buf[MAX_NUMBER];
Line 353  public: Line 445  public:
                                 ptr=services.malloc_atomic(length);                                  ptr=services.malloc_atomic(length);
                                 memcpy(ptr, local_buf, length);                                  memcpy(ptr, local_buf, length);
                                 break;                                  break;
                         }*/                          }
                 /*case DBVT_SINGLE:  */
   /*
                   case DBVT_SINGLE:
                         m_fltVal                           m_fltVal 
                         break;                          break;
         case DBVT_DOUBLE m_dblVal                   case DBVT_DOUBLE m_dblVal 
         case DBVT_STRING m_pstring */                   case DBVT_STRING m_pstring
   */ 
                 case DBVT_DATE:                  case DBVT_DATE:
                         {                          {
                                 char local_buf[MAX_STRING];                                  char local_buf[MAX_STRING];
Line 383  public: Line 478  public:
                 }                  }
         }          }
   
         void getFromString(SQL_Driver_services& services, CString& s, char*& astr, size_t& length) {          void getFromString(SQL_Driver_services& services, CString& s, char*& astr, size_t& length){
                 if(s.IsEmpty()) {                  if(s.IsEmpty()){
                         astr=0;                          astr=0;
                         length=0;                          length=0;
                 } else {                  } else {
Line 395  public: Line 490  public:
                 }                  }
         }          }
   
         void _throw(SQL_Driver_services& services, CException *e) {          modified_statement _preprocess_statement(
                 char szCause[MAX_STRING]; szCause[0]=0;                          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; // 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 Specific");
                           }
                   }
                   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 405  public: Line 565  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() {

Removed from v.1.26.2.1  
changed lines
  Added in v.1.33


E-mail: