|
|
|
/* 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"), "fileopen"), 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"
|