You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/kexi/kexidb/drivers/sqlite/sqliteconnection.cpp

415 lines
11 KiB

/* This file is part of the KDE project
Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
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 <kexidb/driver.h>
#include <kexidb/cursor.h>
#include <kexidb/error.h>
#include <kexiutils/utils.h>
#include <tqfile.h>
#include <tqdir.h>
#include <tqregexp.h>
#include <kgenericfactory.h>
#include <kdebug.h>
//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 << ")" <<endl;
// TQCString st(statement.length()*2);
// st = escapeString( statement.local8Bit() ); //?
#ifdef SQLITE_UTF8
d->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"