Diff for /sql/sqlite/parser3sqlite.C between versions 1.2 and 1.10

version 1.2, 2007/11/26 07:49:36 version 1.10, 2008/07/04 11:56:40
Line 14 Line 14
 #include "sqlite3.h"  #include "sqlite3.h"
 #include "ltdl.h"  #include "ltdl.h"
   
   #define MAX_COLS   500
 #define MAX_STRING 0x400  #define MAX_STRING 0x400
 #define MAX_NUMBER 20  #define MAX_NUMBER 20
   
   #define SQLITE_DEFAULT_CHARSET "UTF-8"
   
 #if _MSC_VER  #if _MSC_VER
 #       define snprintf _snprintf  #       define snprintf _snprintf
 #       define strcasecmp _stricmp  #       define strcasecmp _stricmp
 #endif  #endif
   
 static void MBox(const char *message, const char *title){  
 //      MessageBox(0, (LPCSTR)message, (LPCSTR)title, MB_OK);  
 }  
   
 static char *lsplit(char *string, char delim) {  static char *lsplit(char *string, char delim) {
     if(string) {          if(string) {
                 char *v=strchr(string, delim);                  char *v=strchr(string, delim);
                 if(v) {                  if(v) {
                         *v=0;                          *v=0;
                         return v+1;                          return v+1;
                 }                  }
     }          }
     return 0;          return 0;
 }  }
   
 static char *lsplit(char **string_ref, char delim) {  static char *lsplit(char **string_ref, char delim) {
     char *result=*string_ref;          char *result=*string_ref;
         char *next=lsplit(*string_ref, delim);          char *next=lsplit(*string_ref, delim);
     *string_ref=next;          *string_ref=next;
     return result;          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) {
Line 53  struct Connection { Line 52  struct Connection {
         SQL_Driver_services* services;          SQL_Driver_services* services;
   
         sqlite3* handle;          sqlite3* handle;
         const char* cstrClientCharset;          const char* client_charset;
           bool multi_statements;
         bool autocommit;          bool autocommit;
 };  };
   
Line 70  public: Line 70  public:
   
         /// get api version          /// get api version
         int api_version() { return SQL_DRIVER_API_VERSION; }          int api_version() { return SQL_DRIVER_API_VERSION; }
   
         /// initialize driver by loading sql dynamic link library          /// initialize driver by loading sql dynamic link library
         const char *initialize(char *dlopen_file_spec) {          const char *initialize(char *dlopen_file_spec) {
                 return dlopen_file_spec?                  return dlopen_file_spec?
                         dlink(dlopen_file_spec):"client library column is empty";                          dlink(dlopen_file_spec):"client library column is empty";
         }          }
   
         /**     connect          /**     connect
                 @param url                  @param url
                         format: @b database                          format: @b db-file|:memory:|temporary:?
                         SQLite options - database file name                          autocommit=1&                   // =0 disable autocommit. in this case 1 connect == 1 transaction.
                                                                           //      or you can use begin/commit|rollback explicitly
                           multi_statements=0&             // =1 allow many statements in 1 query
                           ClientCharset=UTF-8             // will transcode to/from specified charset instead of UTF-8 (default for sqlite)
         */          */
         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*
                 ) {                  ){
   
                 int rc;  
   
 //              char *cstrBackwardCompAskServerToTranscode=0;                  Connection& connection=*(Connection *)services.malloc(sizeof(Connection));
                   *connection_ref=&connection;
                 Connection& connection=*(Connection  *)services.malloc(sizeof(Connection));  
                 connection.services=&services;                  connection.services=&services;
   
                 rc = sqlite3_open(url, &connection.handle);                  connection.client_charset=SQLITE_DEFAULT_CHARSET;       
                   connection.multi_statements=false;
                 if( SQLITE_OK != rc ){                  connection.autocommit=true;
                         services._throw(sqlite3_errmsg(connection.handle));  
                         sqlite3_close(connection.handle);                  char* db_path=0;
                   char* db=url;
                   char* options=lsplit(db, '?');
   
                   if(strcmp(db, ":memory:")==0){ // in-memory temporary DB
                           db_path=db;
                   } else if(strcmp(db, ":temporary:")==0){ // on-disk temporary DB
                           // do nothing: empty path mean temporary table on disk
                   } else {
                           char* document_root=(char*)services.request_document_root();
                           if(!document_root) // path to DB-file which was specified by user is path from document_root as anywhere in parser
                                   services._throw("document_root is empty");
   
                           db_path=(char*)services.malloc_atomic(strlen(document_root)+1+strlen(db)+1);
                           strcpy(db_path, document_root);
                           strcat(db_path, "/");
                           strcat(db_path, db);
                 }                  }
   
                 connection.cstrClientCharset=0;                   //services._throw(db_path);
                 connection.autocommit=true;   // значит что все INSERTы и UPDATEы коммитятся автоматически будучи запущенными отдельным запросом  
                 *connection_ref=&connection;  
   
         }  
   
         void exec(Connection& connection, const char* statement) {                  while(options){
                           if(char* key=lsplit(&options, '&')){
                 char *zErr;                                  if(*key) {
                 int rc;                                          if(char* value=lsplit(key, '=')){
                                                   if(strcasecmp(key, "multi_statements")==0){
                 rc = sqlite3_exec(connection.handle, statement, 0, 0, &zErr);                                                          if(atoi(value)!=0)
                                                                   connection.multi_statements=true;
                                                   } else if(strcasecmp(key, "autocommit")==0){
                                                           if(atoi(value)==0)
                                                                   connection.autocommit=false;
                                                           continue;
                                                   } else if(strcmp(key, "ClientCharset")==0){
                                                           toupper_str(value, value, strlen(value));
                                                           connection.client_charset=value;
                                                           continue;
                                                   } else
                                                           services._throw("unknown connect option" /*key*/);
                                           } else 
                                                   services._throw("connect option without =value" /*key*/);
                                   }
                           }
                   }
   
                 MBox(statement, "exec_stat");                  // transcode database_name from $request:charset to UTF-8
                   if(db_path && _transcode_required(connection, SQLITE_DEFAULT_CHARSET)){
                           size_t length=strlen(db_path);
                           services.transcode((const char*)db_path, length,
                                   (const char*&)db_path, length,
                                   services.request_charset(),
                                   SQLITE_DEFAULT_CHARSET);
   
                 if( SQLITE_OK!=rc ){  
                         MBox(zErr, "exec_error");  
                         connection.services->_throw(zErr);  
                         sqlite3_free(zErr);  
                 }                  }
                   
   
                   int rc=sqlite3_open(db_path, &connection.handle);
                   if(rc!=SQLITE_OK){
                           const char* error_msg=sqlite3_errmsg(connection.handle);
                           sqlite3_close(connection.handle);
                           _throw(connection, error_msg);
                   }
                   
                   _begin_transaction(connection);
         }          }
   
         void disconnect(void *aconnection) {          void disconnect(void *aconnection){
                 Connection& connection=*static_cast<Connection*>(aconnection);                  Connection& connection=*static_cast<Connection*>(aconnection);
   
                 sqlite3_close(connection.handle);                  sqlite3_close(connection.handle);
                 connection.handle=0;                  connection.handle=0;
   
                 MBox("disconnect", "disconnect");  
   
         }          }
         void commit(void *aconnection) {  
                 //_asm int 3;  
                 Connection& connection=*static_cast<Connection*>(aconnection);  
                 MBox("commit", "commit");  
   
                 if(!connection.autocommit)          void commit(void *aconnection){
                         exec(connection, "commit");  
         }  
         void rollback(void *aconnection) {  
                 Connection& connection=*static_cast<Connection*>(aconnection);                  Connection& connection=*static_cast<Connection*>(aconnection);
   
                 MBox("rollback", "rollback");  
   
                 if(!connection.autocommit)                  if(!connection.autocommit)
                         exec(connection, "rollback");                          _execute_cmd(connection, "COMMIT");
   
                   _begin_transaction(connection);
         }          }
   
         bool ping(void *aconnection) {          void rollback(void *aconnection){
                 Connection& connection=*static_cast<Connection*>(aconnection);                  Connection& connection=*static_cast<Connection*>(aconnection);
                   if(!connection.autocommit)
                           _execute_cmd(connection, "ROLLBACK");
   
                   _begin_transaction(connection);
           }
   
                 return true;  // пинг не требуется, т.к. сервер не сокетный :)          bool ping(void *aconnection){
                   return true; // not needed
         }          }
   
         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);
                 /*                  /*
                         3.23.22b  
                         You must allocate the to buffer to be at least length*2+1 bytes long.                           You must allocate the to buffer to be at least length*2+1 bytes long. 
                         (In the worse case, each character may need to be encoded as using two bytes,                           In the worse case, each character may need to be encoded as using two bytes, 
                         and you need room for the terminating null byte.)                          and you need room for the terminating null byte.
                 */                  */
                 char *result=(char*)connection.services->malloc_atomic(length*2+1);                  char *result=(char*)connection.services->malloc_atomic(length*2+1);
                 MBox(from, "quote");  
                 char *to=result;                  char *to=result;
                 while(length--) {                  while(length--) {
                         if(*from=='\'') { // ' -> ''                          if(*from=='\'') // ' -> ''
                                 *to++='\'';                                  *to++='\'';
                         }  
                         *to++=*from++;                          *to++=*from++;
                 }                  }
                 *to=0;                  *to=0;
 //              mysql_escape_string(result, from, length);  
                 return result;                  return result;
         }          }
   
         void query(void *aconnection,           void query(void *aconnection, 
                 const char *astatement,                           const char *astatement, 
                 size_t placeholders_count, Placeholder* placeholders,                           size_t placeholders_count, Placeholder* placeholders, 
                 unsigned long offset, unsigned long limit,                          unsigned long offset, unsigned long limit,
                 SQL_Driver_query_event_handlers& handlers) {                          SQL_Driver_query_event_handlers& handlers
                   ){
   
                 Connection& connection=*static_cast<Connection*>(aconnection);                  Connection& connection=*static_cast<Connection*>(aconnection);
                 SQL_Driver_services& services=*connection.services;                  SQL_Driver_services& services=*connection.services;
                 const char* cstrClientCharset=connection.cstrClientCharset;  
   
                 if(placeholders_count>0)                  if(placeholders_count>0)
                         services._throw("bind variables not supported (yet)");                          services._throw("bind variables not supported yet");
                   
                   const char* request_charset=services.request_charset();
                   const char* client_charset=connection.client_charset;
                   bool transcode_needed=_transcode_required(connection);
   
                   // transcode query from $request:charset to ?ClientCharset
                   if(transcode_needed){
                           size_t length=strlen(astatement);
                           services.transcode(astatement, length,
                                   astatement, length,
                                   request_charset,
                                   client_charset);
                   }
                   
                 const char *statement;                  const char *statement;
                 // вот этот блок добавляет в запрос LIMIT N, M если задано. SQLite поддерживает эти параметры                  if(offset || limit!=SQL_NO_LIMIT){
                 if(offset || limit) {  
                         size_t statement_size=strlen(astatement);                          size_t statement_size=strlen(astatement);
                         char *statement_limited=(char *)services.malloc_atomic(                          char *statement_limited=(char *)services.malloc_atomic(
                                 statement_size+MAX_NUMBER*2+8/* limit #,#*/+1);                                  statement_size+MAX_NUMBER*2+8/* LIMIT #,#*/+1);
                         char *cur=statement_limited;                          char *cur=statement_limited;
                         memcpy(cur, astatement, statement_size); cur+=statement_size;                          memcpy(cur, astatement, statement_size);
                         cur+=sprintf(cur, " limit ");                          cur+=statement_size;
                           cur+=sprintf(cur, " LIMIT ");
                         if(offset)                          if(offset)
                                 cur+=snprintf(cur, MAX_NUMBER+1, "%u,", offset);                                  cur+=snprintf(cur, MAX_NUMBER+1, "%u,", offset);
                         if(limit)                          if(limit!=SQL_NO_LIMIT)
                                 cur+=snprintf(cur, MAX_NUMBER, "%u", limit);                                  cur+=snprintf(cur, MAX_NUMBER, "%u", limit);
                         statement=statement_limited;                          statement=statement_limited;
                 } else                  } else
                         statement=astatement;                          statement=astatement;
   
   
                 char *zErr;  
                 const char *pzTail;                  const char *pzTail;
                   int next_statement_length=0;
                 sqlite3_stmt *SQL;                  sqlite3_stmt *SQL;
                 int rc;                  int rc;
                 int i;  
                 SQL_Error sql_error;                  SQL_Error sql_error;
                 bool failed = false;                  bool failed=false;
   
                 do{ // cycling through SQL commands                  do{ // cycling through SQL commands
                           rc=sqlite3_prepare(connection.handle, statement, -1, &SQL, &pzTail);
 //                      MBox(statement, "statement");                          next_statement_length=strlen(pzTail);
                         rc = sqlite3_prepare(connection.handle, statement, -1, &SQL, &pzTail);                          if(rc!=SQLITE_OK){
                         MBox(pzTail, "tail");                                  //sqlite3_free((char*)pzTail);
                                   _throw(connection, sqlite3_errmsg(connection.handle));
                         if( SQLITE_OK!=rc ){                          }
                                 MBox(sqlite3_errmsg(connection.handle), "query_error");                          if(!connection.multi_statements && next_statement_length>0){ // multi statements was not allowed but pzTail point to not empty one
                                 services._throw(sqlite3_errmsg(connection.handle));                                  //sqlite3_free((char*)pzTail);
                                 sqlite3_free(zErr);                                  _throw(connection, "multi statements are not allowed until opption ?multi_statements=1 in connect string is specified.");
                         }                          }
                                                   
                           #define CHECK(afailed) if(afailed){ failed=true; goto cleanup; }
   
                         #define CHECK(afailed) if(afailed) { failed=true; goto cleanup; }                          int column_count=sqlite3_column_count(SQL);
   
                         int column_count = sqlite3_column_count(SQL);                          if(!column_count){ // empty result: insert|delete|update|...
                                   rc=sqlite3_step(SQL);
                         if(!column_count){  // empty result: insert|delete|update|...  
                                 rc = sqlite3_step(SQL);  
                         } else {                          } else {
                                   if(column_count>MAX_COLS)
                                           column_count=MAX_COLS;
   
                                   int column_types[MAX_COLS];
                                   bool transcode_column[MAX_COLS];
   
                                 for(i=0; i<column_count; i++){                                  for(int i=0; i<column_count; i++){
                                         const char *column_name = sqlite3_column_name(SQL, i);                                          const char *column_name=sqlite3_column_name(SQL, i);
                                         size_t length = strlen(column_name);                                          size_t length=strlen(column_name);
   
                                         char* strm=(char*)services.malloc_atomic(length+1);                                          char* strm=(char*)services.malloc_atomic(length+1);
                                         memcpy(strm, column_name, length+1);                                          memcpy(strm, column_name, length+1);
                                           const char* str=strm;
                                         CHECK(handlers.add_column(sql_error, (const char*)strm, length));                                          // transcode column name from ?ClientCharset to $request:charset 
                                           if(transcode_needed){
                                                   services.transcode(str, length,
                                                           str, length,
                                                           client_charset,
                                                           request_charset);
                                           }
                                                           
                                           CHECK(handlers.add_column(sql_error, (const char*)str, length));
                                 }                                  }
                                 CHECK(handlers.before_rows(sql_error));                                  CHECK(handlers.before_rows(sql_error));
   
                                 int column_type;                                  const char *str;
                                 const unsigned char *str;                                  size_t length=0;
                                 size_t length = 0;                                  bool first_row=true;
   
                                 do{                                  do{
                                         rc = sqlite3_step(SQL);                                          rc=sqlite3_step(SQL);
                                         if( rc == SQLITE_ROW ){   // новая строка!!                                          if(rc==SQLITE_ROW){ // new line!!
   
                                                 CHECK(handlers.add_row(sql_error));                                                  CHECK(handlers.add_row(sql_error));
   
                                                 for(i=0; i<column_count; i++){                                                  for(int i=0; i<column_count; i++){
                                                           if(first_row){
                                                                   column_types[i]=sqlite3_column_type(SQL, i);
                                                                   switch(column_types[i]){
                                                                           case SQLITE_INTEGER:
                                                                           case SQLITE_FLOAT:
                                                                           case SQLITE_NULL:
                                                                                   transcode_column[i]=false;
                                                                                   break;
                                                                           default:
                                                                                   transcode_column[i]=transcode_needed;
                                                                                   break;
                                                                   }
                                                           }
   
                                                         column_type = sqlite3_column_type(SQL, i);                                                          // SQLite allow to get value of any type using sqlite3_column_text function
                                                                           switch(column_types[i]){
                                                         // SQLite позволяет поле любого типа получить в виде строки через sqlite3_column_text  
                                                         // просто перекодирует если требуется  
                                                         // а парсер только строковые значения получает  
                                                         // но switch я всё-таки сделал - так, на будущее  
                                                         switch(column_type) {  
                                                                 case SQLITE_TEXT:  
                                                                         str = sqlite3_column_text(SQL, i);  
                                                                         length = strlen((const char*)str);  
                                                                         break;  
                                                                 case SQLITE_INTEGER:  
                                                                         str = sqlite3_column_text(SQL, i);  
                                                                         length = strlen((const char*)str);  
                                                                         break;  
                                                                 case SQLITE_NULL:                                                                  case SQLITE_NULL:
                                                                         str = NULL;                                                                          length=0;
                                                                         length = 0;                                                                          str=NULL;
                                                                           break;
                                                                   case SQLITE_BLOB:
                                                                           str=(const char*)sqlite3_column_blob(SQL, i);
                                                                           length=(size_t)sqlite3_column_bytes(SQL, i);
                                                                         break;                                                                          break;
                                                                 default:                                                                  default: // anything else?
                                                                         str = sqlite3_column_text(SQL, i);                                                                          str=(const char*)sqlite3_column_text(SQL, i);
                                                                         length = strlen((const char*)str);                                                                          length=(size_t)sqlite3_column_bytes(SQL, i);
                                                                         break;                                                                          break;
                                                         }                                                          }
   
                                                         //MBox((const char*)str, "query_in_step");                                                          if(length){
                                                                   char* strm=(char*)services.malloc_atomic(length+1);
                                                         char* strm=(char*)services.malloc_atomic(length+1);                                                                  memcpy(strm, str, length+1);
                                                         memcpy(strm, str, length+1);                                                                  str=strm;
   
                                                         CHECK(handlers.add_row_cell(sql_error, (const char*)strm, length));                                                                  if(transcode_column[i]){
                                                                           // transcode cell value from ?ClientCharset to $request:charset
                                                                           services.transcode(str, length,
                                                                                   str, length,
                                                                                   client_charset,
                                                                                   request_charset);
                                                                   }
                                                           } else
                                                                   str=0;
                                                           
                                                           CHECK(handlers.add_row_cell(sql_error, str, length));
   
                                                 }                                                  }
                                                   first_row=false;
                                         }                                          }
                                 } while( rc == SQLITE_BUSY || rc == SQLITE_ROW );                                  } while(rc==SQLITE_BUSY || rc==SQLITE_ROW);
   
                         }  // if column                          }
   
                         if( rc == SQLITE_ERROR || rc == SQLITE_MISUSE ){                          if(rc==SQLITE_ERROR || rc==SQLITE_MISUSE){
                                 services._throw(sqlite3_errmsg(connection.handle));                                  _throw(connection, sqlite3_errmsg(connection.handle));
                         }                          }
   
         cleanup:          cleanup:
                         sqlite3_finalize(SQL);                          sqlite3_finalize(SQL);
                         statement = pzTail;                          statement=pzTail;
                 } while (strlen(pzTail) > 0);                  } while (next_statement_length>0);
   
                 if(failed)                  if(failed)
                         services._throw(sql_error);                          services._throw(sql_error);
         }          }
   
   private:
           void _begin_transaction(Connection& connection) {
                   if(!connection.autocommit){
                           _execute_cmd(connection, "BEGIN");
                   }
           }
   
           void _execute_cmd(Connection& connection, const char* statement){
                   char* zErr;
                   int rc=sqlite3_exec(connection.handle, statement, 0, 0, &zErr);
                   if(rc!=SQLITE_OK){
                           size_t length=strlen(zErr);
                           char* err_msg=(char *)connection.services->malloc_atomic(length+1);
                           memcpy(err_msg, zErr, length);
   
                           sqlite3_free(zErr);
                           _throw(connection, err_msg);
                   }
   
           }
   
           void _throw(Connection& connection, const char* aerr_msg){
                   size_t length=strlen(aerr_msg);
                   if(length && _transcode_required(connection)){
                           // transcode server error message from ?ClientCharset to $request:charset 
                           connection.services->transcode(aerr_msg, length,
                                   aerr_msg, length,
                                   connection.client_charset,
                                   connection.services->request_charset());
                   }
                   connection.services->_throw(aerr_msg);
           }
   
           bool _transcode_required(Connection& connection, const char* charset=0){
                   return (strcmp(charset?charset:connection.client_charset, connection.services->request_charset())!=0);
           }
   
   
 private: // sqlite client library funcs  private: // sqlite client library funcs
   
         typedef int (*t_sqlite3_open)(const char *filename, sqlite3 **ppDb); t_sqlite3_open sqlite3_open;          typedef int (*t_sqlite3_open)(const char *filename, sqlite3 **ppDb); t_sqlite3_open sqlite3_open;
Line 336  private: // sqlite client library funcs Line 445  private: // sqlite client library funcs
   
         typedef const unsigned char *(* t_sqlite3_column_text)(sqlite3_stmt*, int iCol); t_sqlite3_column_text sqlite3_column_text;          typedef const unsigned char *(* t_sqlite3_column_text)(sqlite3_stmt*, int iCol); t_sqlite3_column_text sqlite3_column_text;
   
           typedef const unsigned char *(* t_sqlite3_column_blob)(sqlite3_stmt*, int iCol); t_sqlite3_column_blob sqlite3_column_blob;
   
           typedef int (* t_sqlite3_column_bytes)(sqlite3_stmt*, int iCol); t_sqlite3_column_bytes sqlite3_column_bytes;
   
   
 private: // sqlite client library funcs linking  private: // sqlite client library funcs linking
   
         const char *dlink(const char *dlopen_file_spec) {          const char *dlink(const char *dlopen_file_spec) {
Line 369  private: // sqlite client library funcs Line 483  private: // sqlite client library funcs
                 DLINK(sqlite3_step);                  DLINK(sqlite3_step);
                 DLINK(sqlite3_column_type);                  DLINK(sqlite3_column_type);
                 DLINK(sqlite3_column_text);                  DLINK(sqlite3_column_text);
                   DLINK(sqlite3_column_blob);
                   DLINK(sqlite3_column_bytes);
                 return 0;                  return 0;
         }          }
   

Removed from v.1.2  
changed lines
  Added in v.1.10


E-mail: