/*************************************************************************** sqlitedataset.cpp (c) 2000-2012 BenoƮt Minisini This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ***************************************************************************/ /********************************************************************** * Copyright (c) 2002, Leo Seib, Hannover * * Project:SQLiteDataset C++ Dynamic Library * Module: SQLiteDataset class realisation file * Author: Leo Seib E-Mail: lev@almaty.pointstrike.net * Begin: 5/04/2002 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **********************************************************************/ #include #include #include #include #include #include #include #include "sqlitedataset.h" #include #include "gambas.h" /**************************************************************/ // BM: my own sqlite3_exec(), that pass the statement pointer to the callback typedef int (*my_sqlite3_callback)(void *, int, char **, char **, sqlite3_stmt *); static int my_sqlite3_exec( sqlite3 *db, /* The database on which the SQL executes */ const char *zSql, /* The SQL to be executed */ my_sqlite3_callback xCallback, /* Invoke this callback routine */ void *pArg, /* First argument to xCallback() */ char **pzErrMsg /* Write error messages here */ ) { int rc = SQLITE_OK; const char *zLeftover; sqlite3_stmt *pStmt = 0; char **azCols = 0; int nRetry = 0; //int nChange = 0; int nCallback; if( zSql==0 ) return SQLITE_OK; while( (rc==SQLITE_OK || (rc==SQLITE_SCHEMA && (++nRetry)<2)) && zSql[0] ){ int nCol; char **azVals = 0; pStmt = 0; rc = sqlite3_prepare(db, zSql, -1, &pStmt, &zLeftover); if( rc!=SQLITE_OK ){ if( pStmt ) sqlite3_finalize(pStmt); continue; } if( !pStmt ){ /* this happens for a comment or white-space */ zSql = zLeftover; continue; } //db->nChange += nChange; nCallback = 0; nCol = sqlite3_column_count(pStmt); if (nCol == 0) goto exec_out; //azCols = sqliteMalloc(2*nCol*sizeof(const char *)); GB.Alloc(POINTER(&azCols), 2*nCol*sizeof(const char *)); if( nCol && !azCols ){ rc = SQLITE_NOMEM; goto exec_out; } while( 1 ){ int i; rc = sqlite3_step(pStmt); /* Invoke the callback function if required */ if( xCallback && (SQLITE_ROW==rc || //(SQLITE_DONE==rc && !nCallback && db->flags&SQLITE_NullCallback)) ){ (SQLITE_DONE==rc && !nCallback && 1)) ){ if( 0==nCallback ){ for(i=0; ipVdbe==0 ){ // nChange = db->nChange; //} if( rc!=SQLITE_SCHEMA ){ nRetry = 0; zSql = zLeftover; while( isspace((unsigned char)zSql[0]) ) zSql++; } break; } } GB.Free(POINTER(&azCols)); azCols = 0; } exec_out: if( pStmt ) sqlite3_finalize(pStmt); if( azCols ) GB.Free(POINTER(&azCols)); //if( sqlite3_malloc_failed ){ // rc = SQLITE_NOMEM; //} // GB.Free(POINTER(pzErrMsg)); // // if( rc!=SQLITE_OK && rc==sqlite3_errcode(db) && pzErrMsg ){ // //*pzErrMsg = malloc(1+strlen(sqlite3_errmsg(db))); // GB.Alloc(POINTER(pzErrMsg), 1+strlen(sqlite3_errmsg(db))); // if( *pzErrMsg ){ // strcpy(*pzErrMsg, sqlite3_errmsg(db)); // } // } return rc; } //************* Callback function *************************** static int callback(void *res_ptr, int ncol, char **reslt, char **cols, sqlite3_stmt *stmt) { /* NG: Type definition */ // typedef vector Tables; Tables tables; Tables::iterator it; char *item; char *table; result_set *r = (result_set *) res_ptr; //dynamic_cast(res_ptr); int sz = r->records.size(); //if (reslt == NULL ) cout << "EMPTY!!!\n"; if (!r->record_header.size()) { /*IF*/ for (int i = 0; i < ncol; i++) { item = strchr(cols[i], (int) '.'); if (!item) { /* Field does not include table info */ item = cols[i]; r->record_header[i].name = item; //NG table = NULL; r->record_header[i].field_table = ""; r->record_header[i].type = ft_String; //default type to string } else { //table = strndup(cols[i], strchr(cols[i], (int)'.') - cols[i]); table = GB.NewString(cols[i], strchr(cols[i], (int) '.') - cols[i]); // table = strdup(reslt[0]); r->record_header[i].name = item + 1; r->record_header[i].field_table = table; r->record_header[i].type = ft_String; //default type to string } if (!table) { /* Field does not contain table info, * so let's default to string. This * has probably happened because aliases * are being used */ } else { /* Check Table Name and add to list */ bool TableRegistered = false; for (it = tables.begin(); it != tables.end(); it++) { if (strcmp((*it).data(), table) == 0) TableRegistered = true; } if (TableRegistered == false) { tables.push_back(table); } } GB.FreeString(&table); //from strdup } SetFieldType(r, tables); // Set all the field types for (int i = 0; i < ncol; i++) { /* Should table name be included in field name */ if (tables.size() > 1) { r->record_header[i].name = cols[i]; } /* if (tables.size() < 1){ r->record_header[i].type = ft_String;//Where type cannot be found //default to string }*/ } } /* IF close */ //sql_record rec; if (reslt != NULL) { for (int i = 0; i < ncol; i++) { //fprintf(stderr, "callback: [%d] %s: %s\n", i, cols[i], reslt[i]); if (reslt[i] == NULL) { r->records[sz][i].set_isNull(r->record_header[i].type); } else { switch (r->record_header[i].type) { case ft_Blob: if (stmt) r->records[sz][i].set_asBlob(reslt[i], sqlite3_column_bytes(stmt, i)); break; default: r->records[sz][i].set_asString(reslt[i], r->record_header[i].type); } } } } //printf("Fsz: [%i]\n",r->record_header.size()); //printf("Recs: [%i]\n",r->records.size()); //cout << " val |" <(db)->getHandle(); } else return NULL; } void SqliteDataset::make_query(StringList & _sql) { string query; try { if (autocommit) db->start_transaction(); if (db == NULL) GB.Error("No database connection"); //close(); for (list < string >::iterator i = _sql.begin(); i != _sql.end(); i++) { query = *i; Dataset::parse_sql(query); //cout << "Executing: "< setErr(sqlite3_exec (this->handle(), query.c_str(), NULL, NULL, NULL)) != SQLITE_OK) { GB.Error(db->getErrorMsg()); } } // end of for if (db->in_transaction() && autocommit) db->commit_transaction(); active = true; ds_state = dsSelect; refresh(); } // end of try catch(...) { if (db->in_transaction()) db->rollback_transaction(); } } void SqliteDataset::make_insert() { make_query(insert_sql); last(); } void SqliteDataset::make_edit() { make_query(update_sql); } void SqliteDataset::make_deletion() { make_query(delete_sql); } // BM: One copy of field_value less by commenting 'edit_object' void SqliteDataset::fill_fields() { //cout <<"rr "<size() == 0) // Filling columns name for (uint i = 0; i < result.record_header.size(); i++) { (*fields_object)[i].props = result.record_header[i]; //(*edit_object)[i].props = result.record_header[i]; } //Filling result if (result.records.size() != 0) { for (uint i = 0; i < result.records[frecno].size(); i++) { (*fields_object)[i].val = result.records[frecno][i]; //(*edit_object)[i].val = result.records[frecno][i]; } } else { field_value tmp; for (uint i = 0; i < result.record_header.size(); i++) { (*fields_object)[i].val = tmp; //(*edit_object)[i].val = ""; } } } //------------- public functions implementation -----------------// // BM: should retry if error = SQLITE_SCHEMA int SqliteDataset::exec(const string & sql) { int res; int retry; if (!handle()) GB.Error("No database connection"); exec_res.record_header.clear(); exec_res.records.clear(); exec_res.conn = handle(); //NG //if ((strncmp("select",sql.c_str(),6) == 0) || (strncmp("SELECT",sql.c_str(),6) == 0)) for (retry = 1; retry <= 2; retry++) { res = sqlite3_exec(handle(), sql.c_str(), old_callback, &exec_res, NULL); if (res != SQLITE_SCHEMA) break; } db->setErr(res); //if (res != SQLITE_OK) // GB.Error(db->getErrorMsg()); return (res == SQLITE_OK); } int SqliteDataset::exec() { return exec(sql); } const void *SqliteDataset::getExecRes() { return &exec_res; } bool SqliteDataset::query(const char *query) { int res; int retry; //try{ if (db == NULL) GB.Error("Database is not defined"); if (dynamic_cast < SqliteDatabase * >(db)->getHandle() == NULL) GB.Error("No database connection"); if ((strncasecmp("select", query, 6) != 0) /*&& (strncasecmp("PRAGMA table",query,12) !=0) && (strncasecmp("PRAGMA index",query,12) !=0) */ ) GB.Error("Syntax error in request: SELECT expected."); //close(); //cout << "Curr size "<setErr(res); if (res == SQLITE_OK) { active = true; ds_state = dsSelect; //cout << "Curr size2 "<first(); //cout << "Curr size3 "<clear(); fields_object->clear(); ds_state = dsInactive; active = false; delete this; } void SqliteDataset::cancel() { if ((ds_state == dsInsert) || (ds_state == dsEdit)) { if (result.record_header.size()) ds_state = dsSelect; else ds_state = dsInactive; } } int SqliteDataset::num_rows() { return result.records.size(); } bool SqliteDataset::eof() { return feof; } bool SqliteDataset::bof() { return fbof; } void SqliteDataset::first() { Dataset::first(); this->fill_fields(); //cout << "In first "<< fields_object->size()<<"\n"; } void SqliteDataset::last() { Dataset::last(); fill_fields(); } void SqliteDataset::prev(void) { Dataset::prev(); fill_fields(); } void SqliteDataset::next(void) { Dataset::next(); if (!eof()) fill_fields(); } //bool SqliteDataset::seek(int pos=0) { bool SqliteDataset::seek(int pos) { if (ds_state == dsSelect) { Dataset::seek(pos); fill_fields(); return true; } return false; } long SqliteDataset::nextid(const char *seq_name) { if (handle()) return db->nextid(seq_name); else return DB_UNEXPECTED_RESULT; } /* Helper function */ void SetFieldType(result_set * r, Tables tables) { Tables::iterator it; sqlite3_stmt *vm; const char *tail; unsigned int len; // result_set res; char sqlcmd[512]; for (it = tables.begin(); it != tables.end(); it++) { sprintf(sqlcmd, "PRAGMA table_info('%s')", (*it).data()); if (sqlite3_prepare(r->conn, sqlcmd, -1, &vm, &tail) != SQLITE_OK) { return; } while (sqlite3_step(vm) == SQLITE_ROW) { /* Type is in 2 */ /* field name is 1 */ /* not null indicator is 3 */ /* dflt value is 4 */ for (uint e = 0; e < r->record_header.size(); e++) { if (r->record_header[e].name == (char *) sqlite3_column_text(vm, 1) && r->record_header[e].field_table == (*it).data()) { r->record_header[e].type = GetFieldType((char *) sqlite3_column_text(vm, 2), &len); r->record_header[e].field_len = len; /* Is not null */ r->record_header[e].notnull = sqlite3_column_text(vm, 3)[0]; } } /* For end */ } /* while end */ sqlite3_finalize(vm); } } /* Return fType and length from String field*/ fType GetFieldType(const char *Type, unsigned int *length) { char *upper; char *_left, *_right; fType rType; /* For return */ unsigned int rTypeLen = 0; int i; upper = GB.NewZeroString(Type); for (i = 0; i < GB.StringLength(upper); i++) upper[i] = toupper(upper[i]); Type = upper; if (!Type) Type = ""; if (strstr(Type, "CHAR(") /* note the opening bracket */ || strstr(Type, "CLOB") || strstr(Type, "TEXT") /* also catches TINYTEXT */ || strstr(Type, "VARCHAR") || strstr(Type, "ENUM") || strstr(Type, "SET") || strstr(Type, "YEAR")) { /* MySQL 2 or 4 digit year (string) */ rType = ft_String; } else if (strstr(Type, "CHAR") /* this is a 1-byte value */ || strstr(Type, "TINYINT") || strstr(Type, "INT1") || strstr(Type, "BOOL")) { rType = ft_Boolean; /* Length is for when value is used as a string */ rTypeLen = ft_Boolean_Length; } else if (strstr(Type, "SMALLINT") || strstr(Type, "INT2")) { rType = ft_Short; /* Length is for when value is used as a string */ rTypeLen = ft_Short_Length; } else if (strstr(Type, "MEDIUMINT")) { rType = ft_Short; /* Length is for when value is used as a string */ rTypeLen = ft_Short_Length; } else if (strstr(Type, "BIGINT") || strstr(Type, "INT8")) { rType = ft_LongDouble; /* Length is for when value is used as a string */ rTypeLen = ft_LongDouble_Length; } else if (strstr(Type, "INTEGER") || strstr(Type, "INT") || strstr(Type, "INT4")) { rType = ft_Long; /* Length is for when value is used as a string */ rTypeLen = ft_Long_Length; } else if (strstr(Type, "DECIMAL") || strstr(Type, "NUMERIC")) { rType = ft_Float; /* Length is for when value is used as a string */ rTypeLen = ft_Float_Length; } else if (strstr(Type, "TIMESTAMP") || strstr(Type, "DATETIME")) { rType = ft_Date; rTypeLen = ft_Date_Length; } else if (strstr(Type, "DATE")) { rType = ft_Date; /* Length is for when value is used as a string */ rTypeLen = ft_Date_Length; } else if (strstr(Type, "TIME")) { rType = ft_Date; /* Length is for when value is used as a string */ rTypeLen = ft_Date_Length; } else if (strstr(Type, "DOUBLE") || strstr(Type, "FLOAT8")) { rType = ft_Double; /* Length is for when value is used as a string */ rTypeLen = ft_Double_Length; } else if (strstr(Type, "REAL") /* this is PostgreSQL "real", not MySQL "real" which is a synonym of "double" */ || strstr(Type, "FLOAT") || strstr(Type, "FLOAT4")) { rType = ft_Float; /* Length is for when value is used as a string */ rTypeLen = ft_Float_Length; } else if (strstr(Type, "BLOB")) // BM { rType = ft_Blob; } else { rType = ft_String; /* most reasonable default */ } if (rType == ft_String) { /* if a length has been defined it will be between () */ _right = (char *)rindex(Type, ')'); _left = (char *)index(Type, '('); if (_right) { _right = '\0'; rTypeLen = atoi(_left + 1); } else { /* set a default length */ rTypeLen = DEFAULT_STRING_LENGTH; } } if (length != NULL) *length = rTypeLen; GB.FreeString(&upper); return rType; }