/* This file is part of the KDE project Copyright (C) 2003-2006 Jaroslaw Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sqliteconnection.h" #include "sqliteconnection_p.h" #include "sqlitecursor.h" #include "sqlitepreparedstatement.h" #include "sqlite.h" #ifndef SQLITE2 # include "kexisql.h" //for isReadOnly() #endif #include #include #include #include #include #include #include #include #include //remove debug #undef KexiDBDrvDbg #define KexiDBDrvDbg if (0) kdDebug() using namespace KexiDB; SQLiteConnectionInternal::SQLiteConnectionInternal(Connection *connection) : ConnectionInternal(connection) , data(0) , data_owned(true) , errmsg_p(0) , res(SQLITE_OK) , temp_st(0x10000) #ifdef SQLITE3 , result_name(0) #endif { } SQLiteConnectionInternal::~SQLiteConnectionInternal() { if (data_owned && data) { free( data ); data = 0; } //sqlite_freemem does this if (errmsg) { // free( errmsg ); // errmsg = 0; // } } void SQLiteConnectionInternal::storeResult() { if (errmsg_p) { errmsg = errmsg_p; sqlite_free(errmsg_p); errmsg_p = 0; } #ifdef SQLITE3 errmsg = (data && res!=SQLITE_OK) ? sqlite3_errmsg(data) : 0; #endif } /*! Used by driver */ SQLiteConnection::SQLiteConnection( Driver *driver, ConnectionData &conn_data ) : Connection( driver, conn_data ) ,d(new SQLiteConnectionInternal(this)) { } SQLiteConnection::~SQLiteConnection() { KexiDBDrvDbg << "SQLiteConnection::~SQLiteConnection()" << endl; //disconnect if was connected // disconnect(); destroy(); delete d; KexiDBDrvDbg << "SQLiteConnection::~SQLiteConnection() ok" << endl; } bool SQLiteConnection::drv_connect(KexiDB::ServerVersionInfo& version) { KexiDBDrvDbg << "SQLiteConnection::connect()" << endl; version.string = TQString(SQLITE_VERSION); //defined in sqlite3.h TQRegExp re("(\\d+)\\.(\\d+)\\.(\\d+)"); if (re.exactMatch(version.string)) { version.major = re.cap(1).toUInt(); version.minor = re.cap(2).toUInt(); version.release = re.cap(3).toUInt(); } return true; } bool SQLiteConnection::drv_disconnect() { KexiDBDrvDbg << "SQLiteConnection::disconnect()" << endl; return true; } bool SQLiteConnection::drv_getDatabasesList( TQStringList &list ) { //this is one-db-per-file database list.append( data()->fileName() ); //more consistent than dbFileName() ? return true; } bool SQLiteConnection::drv_containsTable( const TQString &tableName ) { bool success; return resultExists(TQString("select name from sqlite_master where type='table' and name LIKE %1") .arg(driver()->escapeString(tableName)), success) && success; } bool SQLiteConnection::drv_getTablesList( TQStringList &list ) { KexiDB::Cursor *cursor; m_sql = "select lower(name) from sqlite_master where type='table'"; if (!(cursor = executeQuery( m_sql ))) { KexiDBWarn << "Connection::drv_getTablesList(): !executeQuery()" << endl; return false; } list.clear(); cursor->moveFirst(); while (!cursor->eof() && !cursor->error()) { list += cursor->value(0).toString(); cursor->moveNext(); } if (cursor->error()) { deleteCursor(cursor); return false; } return deleteCursor(cursor); } bool SQLiteConnection::drv_createDatabase( const TQString &dbName ) { // SQLite creates a new db is it does not exist return drv_useDatabase(dbName); #if 0 d->data = sqlite_open( TQFile::encodeName( data()->fileName() ), 0/*mode: unused*/, &d->errmsg_p ); d->storeResult(); return d->data != 0; #endif } bool SQLiteConnection::drv_useDatabase( const TQString &dbName, bool *cancelled, MessageHandler* msgHandler ) { Q_UNUSED(dbName); // KexiDBDrvDbg << "drv_useDatabase(): " << data()->fileName() << endl; #ifdef SQLITE2 Q_UNUSED(cancelled); Q_UNUSED(msgHandler); d->data = sqlite_open( TQFile::encodeName( data()->fileName() ), 0/*mode: unused*/, &d->errmsg_p ); d->storeResult(); return d->data != 0; #else //SQLITE3 //TODO: perhaps allow to use sqlite3_open16() as well for SQLite ~ 3.3 ? //! @todo add option (command line or in kexirc?) int exclusiveFlag = Connection::isReadOnly() ? SQLITE_OPEN_READONLY : SQLITE_OPEN_WRITE_LOCKED; // <-- shared read + (if !r/o): exclusive write //! @todo add option int allowReadonly = 1; const bool wasReadOnly = Connection::isReadOnly(); d->res = sqlite3_open( //TQFile::encodeName( data()->fileName() ), data()->fileName().utf8(), /* unicode expected since SQLite 3.1 */ &d->data, exclusiveFlag, allowReadonly /* If 1 and locking fails, try opening in read-only mode */ ); d->storeResult(); if (d->res == SQLITE_OK && cancelled && !wasReadOnly && allowReadonly && isReadOnly()) { //opened as read only, ask if (KMessageBox::Continue != askQuestion( i18n("Do you want to open file \"%1\" as read-only?") .arg(TQDir::convertSeparators(data()->fileName())) + "\n\n" + i18n("The file is probably already open on this or another computer.") + " " + i18n("Could not gain exclusive access for writing the file."), KMessageBox::WarningContinueCancel, KMessageBox::Continue, KGuiItem(i18n("Open As Read-Only"), "document-open"), KStdGuiItem::cancel(), "askBeforeOpeningFileReadOnly", KMessageBox::Notify, msgHandler )) { clearError(); if (!drv_closeDatabase()) return false; *cancelled = true; return false; } } if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_READWRITE) { setError(ERR_ACCESS_RIGHTS, i18n("The file is probably already open on this or another computer.")+"\n\n" + i18n("Could not gain exclusive access for reading and writing the file.") + " " + i18n("Check the file's permissions and whether it is already opened and locked by another application.")); } else if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_WRITE) { setError(ERR_ACCESS_RIGHTS, i18n("The file is probably already open on this or another computer.")+"\n\n" + i18n("Could not gain exclusive access for writing the file.") + " " + i18n("Check the file's permissions and whether it is already opened and locked by another application.")); } return d->res == SQLITE_OK; #endif } bool SQLiteConnection::drv_closeDatabase() { if (!d->data) return false; #ifdef SQLITE2 sqlite_close(d->data); d->data = 0; return true; #else const int res = sqlite_close(d->data); if (SQLITE_OK == res) { d->data = 0; return true; } if (SQLITE_BUSY==res) { #if 0 //this is ANNOYING, needs fixing (by closing cursors or waiting) setError(ERR_CLOSE_FAILED, i18n("Could not close busy database.")); #else return true; #endif } return false; #endif } bool SQLiteConnection::drv_dropDatabase( const TQString &dbName ) { Q_UNUSED(dbName); // Each database is one single SQLite file. const TQString filename = data()->fileName(); if (TQFile(filename).exists() && !TQDir().remove(filename)) { setError(ERR_ACCESS_RIGHTS, i18n("Could not remove file \"%1\".") .arg(TQDir::convertSeparators(filename)) + " " + i18n("Check the file's permissions and whether it is already opened and locked by another application.")); return false; } return true; } //CursorData* SQLiteConnection::drv_createCursor( const TQString& statement ) Cursor* SQLiteConnection::prepareQuery( const TQString& statement, uint cursor_options ) { return new SQLiteCursor( this, statement, cursor_options ); } Cursor* SQLiteConnection::prepareQuery( QuerySchema& query, uint cursor_options ) { return new SQLiteCursor( this, query, cursor_options ); } bool SQLiteConnection::drv_executeSQL( const TQString& statement ) { // KexiDBDrvDbg << "SQLiteConnection::drv_executeSQL(" << statement << ")" <temp_st = statement.utf8(); #else d->temp_st = statement.local8Bit(); //latin1 only #endif #ifdef KEXI_DEBUG_GUI KexiUtils::addKexiDBDebug(TQString("ExecuteSQL (SQLite): ")+statement); #endif d->res = sqlite_exec( d->data, (const char*)d->temp_st, 0/*callback*/, 0, &d->errmsg_p ); d->storeResult(); #ifdef KEXI_DEBUG_GUI KexiUtils::addKexiDBDebug(d->res==SQLITE_OK ? " Success" : " Failure"); #endif return d->res==SQLITE_OK; } TQ_ULLONG SQLiteConnection::drv_lastInsertRowID() { return (TQ_ULLONG)sqlite_last_insert_rowid(d->data); } int SQLiteConnection::serverResult() { return d->res==0 ? Connection::serverResult() : d->res; } TQString SQLiteConnection::serverResultName() { TQString r = #ifdef SQLITE2 TQString::fromLatin1( sqlite_error_string(d->res) ); #else //SQLITE3 TQString(); //fromLatin1( d->result_name ); #endif return r.isEmpty() ? Connection::serverResultName() : r; } void SQLiteConnection::drv_clearServerResult() { if (!d) return; d->res = SQLITE_OK; #ifdef SQLITE2 d->errmsg_p = 0; #else // d->result_name = 0; #endif } TQString SQLiteConnection::serverErrorMsg() { return d->errmsg.isEmpty() ? Connection::serverErrorMsg() : d->errmsg; } PreparedStatement::Ptr SQLiteConnection::prepareStatement(PreparedStatement::StatementType type, FieldList& fields) { //#ifndef SQLITE2 //TEMP IFDEF! return new SQLitePreparedStatement(type, *d, fields); //#endif } bool SQLiteConnection::isReadOnly() const { #ifdef SQLITE2 return Connection::isReadOnly(); #else return d->data ? sqlite3_is_readonly(d->data) : false; #endif } #ifdef SQLITE2 bool SQLiteConnection::drv_alterTableName(TableSchema& tableSchema, const TQString& newName, bool replace) { const TQString oldTableName = tableSchema.name(); const bool destTableExists = this->tableSchema( newName ) != 0; //1. drop the table if (destTableExists) { if (!replace) return false; if (!drv_dropTable( newName )) return false; } //2. create a copy of the table //TODO: move this code to drv_copyTable() tableSchema.setName(newName); //helper: #define drv_alterTableName_ERR \ tableSchema.setName(oldTableName) //restore old name if (!drv_createTable( tableSchema )) { drv_alterTableName_ERR; return false; } //TODO indices, etc.??? // 3. copy all rows to the new table if (!executeSQL(TQString::fromLatin1("INSERT INTO %1 SELECT * FROM %2") .arg(escapeIdentifier(tableSchema.name())).arg(escapeIdentifier(oldTableName)))) { drv_alterTableName_ERR; return false; } // 4. drop old table. if (!drv_dropTable( oldTableName )) { drv_alterTableName_ERR; return false; } return true; } #endif #include "sqliteconnection.moc"