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/connection.cpp

3553 lines
112 KiB

/* This file is part of the KDE project
Copyright (C) 2003-2007 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 <kexidb/connection.h>
#include "error.h"
#include "connection_p.h"
#include "connectiondata.h"
#include "driver.h"
#include "driver_p.h"
#include "schemadata.h"
#include "tableschema.h"
#include "relationship.h"
#include "transaction.h"
#include "cursor.h"
#include "global.h"
#include "roweditbuffer.h"
#include "utils.h"
#include "dbproperties.h"
#include "lookupfieldschema.h"
#include "parser/parser.h"
#include <kexiutils/utils.h>
#include <kexiutils/identifier.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qguardedptr.h>
#include <qdom.h>
#include <klocale.h>
#include <kdebug.h>
#define KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION 1
//#define KEXIDB_LOOKUP_FIELD_TEST
namespace KexiDB {
Connection::SelectStatementOptions::SelectStatementOptions()
: identifierEscaping(Driver::EscapeDriver|Driver::EscapeAsNecessary)
, alsoRetrieveROWID(false)
, addVisibleLookupColumns(true)
{
}
Connection::SelectStatementOptions::~SelectStatementOptions()
{
}
//================================================
ConnectionInternal::ConnectionInternal(Connection *conn)
: connection(conn)
{
}
ConnectionInternal::~ConnectionInternal()
{
}
//================================================
//! @internal
class ConnectionPrivate
{
public:
ConnectionPrivate(Connection* const conn, ConnectionData &conn_data)
: conn(conn)
, conn_data(&conn_data)
, tableSchemaChangeListeners(101)
, m_parser(0)
, tables_byname(101, false)
, queries_byname(101, false)
, kexiDBSystemTables(101)
, dont_remove_transactions(false)
, skip_databaseExists_check_in_useDatabase(false)
, default_trans_started_inside(false)
, isConnected(false)
, autoCommit(true)
{
tableSchemaChangeListeners.setAutoDelete(true);
obsoleteQueries.setAutoDelete(true);
tables.setAutoDelete(true);
tables_byname.setAutoDelete(false);//tables is owner, not me
kexiDBSystemTables.setAutoDelete(true);//only system tables
queries.setAutoDelete(true);
queries_byname.setAutoDelete(false);//queries is owner, not me
//reasonable sizes: TODO
tables.resize(101);
queries.resize(101);
}
~ConnectionPrivate()
{
delete m_parser;
}
void errorInvalidDBContents(const QString& details) {
conn->setError( ERR_INVALID_DATABASE_CONTENTS, i18n("Invalid database contents. ")+details);
}
QString strItIsASystemObject() const {
return i18n("It is a system object.");
}
inline Parser *parser() { return m_parser ? m_parser : (m_parser = new Parser(conn)); }
Connection* const conn; //!< The \a Connection instance this \a ConnectionPrivate belongs to.
QGuardedPtr<ConnectionData> conn_data; //!< the \a ConnectionData used within that connection.
/*! Default transaction handle.
If transactions are supported: Any operation on database (e.g. inserts)
that is started without specifying transaction context, will be performed
in the context of this transaction. */
Transaction default_trans;
QValueList<Transaction> transactions;
QPtrDict< QPtrList<Connection::TableSchemaChangeListenerInterface> > tableSchemaChangeListeners;
//! Used in Connection::setQuerySchemaObsolete( const QString& queryName )
//! to collect obsolete queries. THese are deleted on connection deleting.
QPtrList<QuerySchema> obsoleteQueries;
//! server version information for this connection.
KexiDB::ServerVersionInfo serverVersion;
//! Daabase version information for this connection.
KexiDB::DatabaseVersionInfo databaseVersion;
Parser *m_parser;
//! Table schemas retrieved on demand with tableSchema()
QIntDict<TableSchema> tables;
QDict<TableSchema> tables_byname;
QIntDict<QuerySchema> queries;
QDict<QuerySchema> queries_byname;
//! used just for removing system TableSchema objects on db close.
QPtrDict<TableSchema> kexiDBSystemTables;
//! Database properties
DatabaseProperties* dbProperties;
QString availableDatabaseName; //!< used by anyAvailableDatabaseName()
QString usedDatabase; //!< database name that is opened now (the currentDatabase() name)
//! true if rollbackTransaction() and commitTransaction() shouldn't remove
//! the transaction object from 'transactions' list; used by closeDatabase()
bool dont_remove_transactions : 1;
//! used to avoid endless recursion between useDatabase() and databaseExists()
//! when useTemporaryDatabaseIfNeeded() works
bool skip_databaseExists_check_in_useDatabase : 1;
/*! Used when single transactions are only supported (Driver::SingleTransactions).
True value means default transaction has been started inside connection object
(by beginAutoCommitTransaction()), otherwise default transaction has been started outside
of the object (e.g. before createTable()), so we shouldn't autocommit the transaction
in commitAutoCommitTransaction(). Also, beginAutoCommitTransaction() doesn't restarts
transaction if default_trans_started_inside is false. Such behaviour allows user to
execute a sequence of actions like CREATE TABLE...; INSERT DATA...; within a single transaction
and commit it or rollback by hand. */
bool default_trans_started_inside : 1;
bool isConnected : 1;
bool autoCommit : 1;
/*! True for read only connection. Used especially for file-based drivers. */
bool readOnly : 1;
};
}//namespace KexiDB
//================================================
using namespace KexiDB;
//! static: list of internal KexiDB system table names
QStringList KexiDB_kexiDBSystemTableNames;
Connection::Connection( Driver *driver, ConnectionData &conn_data )
: QObject()
,KexiDB::Object()
,d(new ConnectionPrivate(this, conn_data))
,m_driver(driver)
,m_destructor_started(false)
{
d->dbProperties = new DatabaseProperties(this);
m_cursors.setAutoDelete(true);
// d->transactions.setAutoDelete(true);
//reasonable sizes: TODO
m_cursors.resize(101);
// d->transactions.resize(101);//woohoo! so many transactions?
m_sql.reserve(0x4000);
}
void Connection::destroy()
{
disconnect();
//do not allow the driver to touch me: I will kill myself.
m_driver->d->connections.take( this );
}
Connection::~Connection()
{
m_destructor_started = true;
// KexiDBDbg << "Connection::~Connection()" << endl;
delete d->dbProperties;
delete d;
d = 0;
/* if (m_driver) {
if (m_is_connected) {
//delete own table schemas
d->tables.clear();
//delete own cursors:
m_cursors.clear();
}
//do not allow the driver to touch me: I will kill myself.
m_driver->m_connections.take( this );
}*/
}
ConnectionData* Connection::data() const
{
return d->conn_data;
}
bool Connection::connect()
{
clearError();
if (d->isConnected) {
setError(ERR_ALREADY_CONNECTED, i18n("Connection already established.") );
return false;
}
d->serverVersion.clear();
if (!(d->isConnected = drv_connect(d->serverVersion))) {
setError(m_driver->isFileDriver() ?
i18n("Could not open \"%1\" project file.").arg(QDir::convertSeparators(d->conn_data->fileName()))
: i18n("Could not connect to \"%1\" database server.").arg(d->conn_data->serverInfoString()) );
}
return d->isConnected;
}
bool Connection::isDatabaseUsed() const
{
return !d->usedDatabase.isEmpty() && d->isConnected && drv_isDatabaseUsed();
}
void Connection::clearError()
{
Object::clearError();
m_sql = QString::null;
}
bool Connection::disconnect()
{
clearError();
if (!d->isConnected)
return true;
if (!closeDatabase())
return false;
bool ok = drv_disconnect();
if (ok)
d->isConnected = false;
return ok;
}
bool Connection::isConnected() const
{
return d->isConnected;
}
bool Connection::checkConnected()
{
if (d->isConnected) {
clearError();
return true;
}
setError(ERR_NO_CONNECTION, i18n("Not connected to the database server.") );
return false;
}
bool Connection::checkIsDatabaseUsed()
{
if (isDatabaseUsed()) {
clearError();
return true;
}
setError(ERR_NO_DB_USED, i18n("Currently no database is used.") );
return false;
}
QStringList Connection::databaseNames(bool also_system_db)
{
KexiDBDbg << "Connection::databaseNames("<<also_system_db<<")"<< endl;
if (!checkConnected())
return QStringList();
QString tmpdbName;
//some engines need to have opened any database before executing "create database"
if (!useTemporaryDatabaseIfNeeded(tmpdbName))
return QStringList();
QStringList list, non_system_list;
bool ret = drv_getDatabasesList( list );
if (!tmpdbName.isEmpty()) {
//whatever result is - now we have to close temporary opened database:
if (!closeDatabase())
return QStringList();
}
if (!ret)
return QStringList();
if (also_system_db)
return list;
//filter system databases:
for (QStringList::ConstIterator it = list.constBegin(); it!=list.constEnd(); ++it) {
KexiDBDbg << "Connection::databaseNames(): " << *it << endl;
if (!m_driver->isSystemDatabaseName(*it)) {
KexiDBDbg << "add " << *it << endl;
non_system_list << (*it);
}
}
return non_system_list;
}
bool Connection::drv_getDatabasesList( QStringList &list )
{
list.clear();
return true;
}
bool Connection::drv_databaseExists( const QString &dbName, bool ignoreErrors )
{
QStringList list = databaseNames(true);//also system
if (error()) {
return false;
}
if (list.find( dbName )==list.end()) {
if (!ignoreErrors)
setError(ERR_OBJECT_NOT_FOUND, i18n("The database \"%1\" does not exist.").arg(dbName));
return false;
}
return true;
}
bool Connection::databaseExists( const QString &dbName, bool ignoreErrors )
{
// KexiDBDbg << "Connection::databaseExists(" << dbName << "," << ignoreErrors << ")" << endl;
if (!checkConnected())
return false;
clearError();
if (m_driver->isFileDriver()) {
//for file-based db: file must exists and be accessible
//js: moved from useDatabase():
QFileInfo file(d->conn_data->fileName());
if (!file.exists() || ( !file.isFile() && !file.isSymLink()) ) {
if (!ignoreErrors)
setError(ERR_OBJECT_NOT_FOUND, i18n("Database file \"%1\" does not exist.")
.arg(QDir::convertSeparators(d->conn_data->fileName())) );
return false;
}
if (!file.isReadable()) {
if (!ignoreErrors)
setError(ERR_ACCESS_RIGHTS, i18n("Database file \"%1\" is not readable.")
.arg(QDir::convertSeparators(d->conn_data->fileName())) );
return false;
}
if (!file.isWritable()) {
if (!ignoreErrors)
setError(ERR_ACCESS_RIGHTS, i18n("Database file \"%1\" is not writable.")
.arg(QDir::convertSeparators(d->conn_data->fileName())) );
return false;
}
return true;
}
QString tmpdbName;
//some engines need to have opened any database before executing "create database"
const bool orig_skip_databaseExists_check_in_useDatabase = d->skip_databaseExists_check_in_useDatabase;
d->skip_databaseExists_check_in_useDatabase = true;
bool ret = useTemporaryDatabaseIfNeeded(tmpdbName);
d->skip_databaseExists_check_in_useDatabase = orig_skip_databaseExists_check_in_useDatabase;
if (!ret)
return false;
ret = drv_databaseExists(dbName, ignoreErrors);
if (!tmpdbName.isEmpty()) {
//whatever result is - now we have to close temporary opened database:
if (!closeDatabase())
return false;
}
return ret;
}
#define createDatabase_CLOSE \
{ if (!closeDatabase()) { \
setError(i18n("Database \"%1\" created but could not be closed after creation.").arg(dbName) ); \
return false; \
} }
#define createDatabase_ERROR \
{ createDatabase_CLOSE; return false; }
bool Connection::createDatabase( const QString &dbName )
{
if (!checkConnected())
return false;
if (databaseExists( dbName )) {
setError(ERR_OBJECT_EXISTS, i18n("Database \"%1\" already exists.").arg(dbName) );
return false;
}
if (m_driver->isSystemDatabaseName( dbName )) {
setError(ERR_SYSTEM_NAME_RESERVED,
i18n("Cannot create database \"%1\". This name is reserved for system database.").arg(dbName) );
return false;
}
if (m_driver->isFileDriver()) {
//update connection data if filename differs
d->conn_data->setFileName( dbName );
}
QString tmpdbName;
//some engines need to have opened any database before executing "create database"
if (!useTemporaryDatabaseIfNeeded(tmpdbName))
return false;
//low-level create
if (!drv_createDatabase( dbName )) {
setError(i18n("Error creating database \"%1\" on the server.").arg(dbName) );
closeDatabase();//sanity
return false;
}
if (!tmpdbName.isEmpty()) {
//whatever result is - now we have to close temporary opened database:
if (!closeDatabase())
return false;
}
if (!tmpdbName.isEmpty() || !m_driver->d->isDBOpenedAfterCreate) {
//db need to be opened
if (!useDatabase( dbName, false/*not yet kexi compatible!*/ )) {
setError(i18n("Database \"%1\" created but could not be opened.").arg(dbName) );
return false;
}
}
else {
//just for the rule
d->usedDatabase = dbName;
}
Transaction trans;
if (m_driver->transactionsSupported()) {
trans = beginTransaction();
if (!trans.active())
return false;
}
//not needed since closeDatabase() rollbacks transaction: TransactionGuard trans_g(this);
// if (error())
// return false;
//-create system tables schema objects
if (!setupKexiDBSystemSchema())
return false;
//-physically create system tables
for (QPtrDictIterator<TableSchema> it(d->kexiDBSystemTables); it.current(); ++it) {
if (!drv_createTable( it.current()->name() ))
createDatabase_ERROR;
}
/* moved to KexiProject...
//-create default part info
TableSchema *ts;
if (!(ts = tableSchema("kexi__parts")))
createDatabase_ERROR;
FieldList *fl = ts->subList("p_id", "p_name", "p_mime", "p_url");
if (!fl)
createDatabase_ERROR;
if (!insertRecord(*fl, QVariant(1), QVariant("Tables"), QVariant("kexi/table"), QVariant("http://koffice.org/kexi/")))
createDatabase_ERROR;
if (!insertRecord(*fl, QVariant(2), QVariant("Queries"), QVariant("kexi/query"), QVariant("http://koffice.org/kexi/")))
createDatabase_ERROR;
*/
//-insert KexiDB version info:
TableSchema *t_db = tableSchema("kexi__db");
if (!t_db)
createDatabase_ERROR;
if ( !insertRecord(*t_db, "kexidb_major_ver", KexiDB::version().major)
|| !insertRecord(*t_db, "kexidb_minor_ver", KexiDB::version().minor))
createDatabase_ERROR;
if (trans.active() && !commitTransaction(trans))
createDatabase_ERROR;
createDatabase_CLOSE;
return true;
}
#undef createDatabase_CLOSE
#undef createDatabase_ERROR
bool Connection::useDatabase( const QString &dbName, bool kexiCompatible, bool *cancelled, MessageHandler* msgHandler )
{
if (cancelled)
*cancelled = false;
KexiDBDbg << "Connection::useDatabase(" << dbName << "," << kexiCompatible <<")" << endl;
if (!checkConnected())
return false;
if (dbName.isEmpty())
return false;
QString my_dbName = dbName;
// if (my_dbName.isEmpty()) {
// const QStringList& db_lst = databaseNames();
// if (!db_lst.isEmpty())
// my_dbName = db_lst.first();
// }
if (d->usedDatabase == my_dbName)
return true; //already used
if (!d->skip_databaseExists_check_in_useDatabase) {
if (!databaseExists(my_dbName, false /*don't ignore errors*/))
return false; //database must exist
}
if (!d->usedDatabase.isEmpty() && !closeDatabase()) //close db if already used
return false;
d->usedDatabase = "";
if (!drv_useDatabase( my_dbName, cancelled, msgHandler )) {
if (cancelled && *cancelled)
return false;
QString msg(i18n("Opening database \"%1\" failed.").arg( my_dbName ));
if (error())
setError( this, msg );
else
setError( msg );
return false;
}
//-create system tables schema objects
if (!setupKexiDBSystemSchema())
return false;
if (kexiCompatible && my_dbName.lower()!=anyAvailableDatabaseName().lower()) {
//-get global database information
int num;
bool ok;
// static QString notfound_str = i18n("\"%1\" database property not found");
num = d->dbProperties->value("kexidb_major_ver").toInt(&ok);
if (!ok)
return false;
d->databaseVersion.major = num;
/* if (true!=querySingleNumber(
"select db_value from kexi__db where db_property=" + m_driver->escapeString(QString("kexidb_major_ver")), num)) {
d->errorInvalidDBContents(notfound_str.arg("kexidb_major_ver"));
return false;
}*/
num = d->dbProperties->value("kexidb_minor_ver").toInt(&ok);
if (!ok)
return false;
d->databaseVersion.minor = num;
/* if (true!=querySingleNumber(
"select db_value from kexi__db where db_property=" + m_driver->escapeString(QString("kexidb_minor_ver")), num)) {
d->errorInvalidDBContents(notfound_str.arg("kexidb_minor_ver"));
return false;
}*/
#if 0 //this is already checked in DriverManagerInternal::lookupDrivers()
//** error if major version does not match
if (m_driver->versionMajor()!=KexiDB::versionMajor()) {
setError(ERR_INCOMPAT_DATABASE_VERSION,
i18n("Database version (%1) does not match Kexi application's version (%2)")
.arg( QString("%1.%2").arg(versionMajor()).arg(versionMinor()) )
.arg( QString("%1.%2").arg(KexiDB::versionMajor()).arg(KexiDB::versionMinor()) ) );
return false;
}
if (m_driver->versionMinor()!=KexiDB::versionMinor()) {
//js TODO: COMPATIBILITY CODE HERE!
//js TODO: CONVERSION CODE HERE (or signal that conversion is needed)
}
#endif
}
d->usedDatabase = my_dbName;
return true;
}
bool Connection::closeDatabase()
{
if (d->usedDatabase.isEmpty())
return true; //no db used
if (!checkConnected())
return true;
bool ret = true;
/*! \todo (js) add CLEVER algorithm here for nested transactions */
if (m_driver->transactionsSupported()) {
//rollback all transactions
QValueList<Transaction>::ConstIterator it;
d->dont_remove_transactions=true; //lock!
for (it=d->transactions.constBegin(); it!= d->transactions.constEnd(); ++it) {
if (!rollbackTransaction(*it)) {//rollback as much as you can, don't stop on prev. errors
ret = false;
}
else {
KexiDBDbg << "Connection::closeDatabase(): transaction rolled back!" << endl;
KexiDBDbg << "Connection::closeDatabase(): trans.refcount==" <<
((*it).m_data ? QString::number((*it).m_data->refcount) : "(null)") << endl;
}
}
d->dont_remove_transactions=false; //unlock!
d->transactions.clear(); //free trans. data
}
//delete own cursors:
m_cursors.clear();
//delete own schemas
d->tables.clear();
d->kexiDBSystemTables.clear();
d->queries.clear();
if (!drv_closeDatabase())
return false;
d->usedDatabase = "";
// KexiDBDbg << "Connection::closeDatabase(): " << ret << endl;
return ret;
}
QString Connection::currentDatabase() const
{
return d->usedDatabase;
}
bool Connection::useTemporaryDatabaseIfNeeded(QString &tmpdbName)
{
if (!m_driver->isFileDriver() && m_driver->beh->USING_DATABASE_REQUIRED_TO_CONNECT
&& !isDatabaseUsed()) {
//we have no db used, but it is required by engine to have used any!
tmpdbName = anyAvailableDatabaseName();
if (tmpdbName.isEmpty()) {
setError(ERR_NO_DB_USED, i18n("Cannot find any database for temporary connection.") );
return false;
}
const bool orig_skip_databaseExists_check_in_useDatabase = d->skip_databaseExists_check_in_useDatabase;
d->skip_databaseExists_check_in_useDatabase = true;
bool ret = useDatabase(tmpdbName, false);
d->skip_databaseExists_check_in_useDatabase = orig_skip_databaseExists_check_in_useDatabase;
if (!ret) {
setError(errorNum(),
i18n("Error during starting temporary connection using \"%1\" database name.")
.arg(tmpdbName) );
return false;
}
}
return true;
}
bool Connection::dropDatabase( const QString &dbName )
{
if (!checkConnected())
return false;
QString dbToDrop;
if (dbName.isEmpty() && d->usedDatabase.isEmpty()) {
if (!m_driver->isFileDriver()
|| (m_driver->isFileDriver() && d->conn_data->fileName().isEmpty()) ) {
setError(ERR_NO_NAME_SPECIFIED, i18n("Cannot drop database - name not specified.") );
return false;
}
//this is a file driver so reuse previously passed filename
dbToDrop = d->conn_data->fileName();
}
else {
if (dbName.isEmpty()) {
dbToDrop = d->usedDatabase;
} else {
if (m_driver->isFileDriver()) //lets get full path
dbToDrop = QFileInfo(dbName).absFilePath();
else
dbToDrop = dbName;
}
}
if (dbToDrop.isEmpty()) {
setError(ERR_NO_NAME_SPECIFIED, i18n("Cannot delete database - name not specified.") );
return false;
}
if (m_driver->isSystemDatabaseName( dbToDrop )) {
setError(ERR_SYSTEM_NAME_RESERVED, i18n("Cannot delete system database \"%1\".").arg(dbToDrop) );
return false;
}
if (isDatabaseUsed() && d->usedDatabase == dbToDrop) {
//we need to close database because cannot drop used this database
if (!closeDatabase())
return false;
}
QString tmpdbName;
//some engines need to have opened any database before executing "drop database"
if (!useTemporaryDatabaseIfNeeded(tmpdbName))
return false;
//ok, now we have access to dropping
bool ret = drv_dropDatabase( dbToDrop );
if (!tmpdbName.isEmpty()) {
//whatever result is - now we have to close temporary opened database:
if (!closeDatabase())
return false;
}
return ret;
}
QStringList Connection::objectNames(int objType, bool* ok)
{
QStringList list;
if (!checkIsDatabaseUsed()) {
if(ok)
*ok = false;
return list;
}
QString sql;
if (objType==KexiDB::AnyObjectType)
sql = "SELECT o_name FROM kexi__objects";
else
sql = QString::fromLatin1("SELECT o_name FROM kexi__objects WHERE o_type=%1").arg(objType);
Cursor *c = executeQuery(sql);
if (!c) {
if(ok)
*ok = false;
return list;
}
for (c->moveFirst(); !c->eof(); c->moveNext()) {
QString name = c->value(0).toString();
if (KexiUtils::isIdentifier( name )) {
list.append(name);
}
}
if (!deleteCursor(c)) {
if(ok)
*ok = false;
return list;
}
if(ok)
*ok = true;
return list;
}
QStringList Connection::tableNames(bool also_system_tables)
{
bool ok = true;
QStringList list = objectNames(TableObjectType, &ok);
if (also_system_tables && ok) {
list += Connection::kexiDBSystemTableNames();
}
return list;
}
//! \todo (js): this will depend on KexiDB lib version
const QStringList& Connection::kexiDBSystemTableNames()
{
if (KexiDB_kexiDBSystemTableNames.isEmpty()) {
KexiDB_kexiDBSystemTableNames
<< "kexi__objects"
<< "kexi__objectdata"
<< "kexi__fields"
// << "kexi__querydata"
// << "kexi__queryfields"
// << "kexi__querytables"
<< "kexi__db"
;
}
return KexiDB_kexiDBSystemTableNames;
}
KexiDB::ServerVersionInfo* Connection::serverVersion() const
{
return isConnected() ? &d->serverVersion : 0;
}
KexiDB::DatabaseVersionInfo* Connection::databaseVersion() const
{
return isDatabaseUsed() ? &d->databaseVersion : 0;
}
DatabaseProperties& Connection::databaseProperties()
{
return *d->dbProperties;
}
QValueList<int> Connection::tableIds()
{
return objectIds(KexiDB::TableObjectType);
}
QValueList<int> Connection::queryIds()
{
return objectIds(KexiDB::QueryObjectType);
}
QValueList<int> Connection::objectIds(int objType)
{
QValueList<int> list;
if (!checkIsDatabaseUsed())
return list;
Cursor *c = executeQuery(
QString::fromLatin1("SELECT o_id, o_name FROM kexi__objects WHERE o_type=%1").arg(objType));
if (!c)
return list;
for (c->moveFirst(); !c->eof(); c->moveNext())
{
QString tname = c->value(1).toString(); //kexi__objects.o_name
if (KexiUtils::isIdentifier( tname )) {
list.append(c->value(0).toInt()); //kexi__objects.o_id
}
}
deleteCursor(c);
return list;
}
QString Connection::createTableStatement( const KexiDB::TableSchema& tableSchema ) const
{
// Each SQL identifier needs to be escaped in the generated query.
QString sql;
sql.reserve(4096);
sql = "CREATE TABLE " + escapeIdentifier(tableSchema.name()) + " (";
bool first=true;
Field::ListIterator it( tableSchema.m_fields );
Field *field;
for (;(field = it.current())!=0; ++it) {
if (first)
first = false;
else
sql += ", ";
QString v = escapeIdentifier(field->name()) + " ";
const bool autoinc = field->isAutoIncrement();
const bool pk = field->isPrimaryKey() || (autoinc && m_driver->beh->AUTO_INCREMENT_REQUIRES_PK);
//TODO: warning: ^^^^^ this allows only one autonumber per table when AUTO_INCREMENT_REQUIRES_PK==true!
if (autoinc && m_driver->beh->SPECIAL_AUTO_INCREMENT_DEF) {
if (pk)
v += m_driver->beh->AUTO_INCREMENT_TYPE + " " + m_driver->beh->AUTO_INCREMENT_PK_FIELD_OPTION;
else
v += m_driver->beh->AUTO_INCREMENT_TYPE + " " + m_driver->beh->AUTO_INCREMENT_FIELD_OPTION;
}
else {
if (autoinc && !m_driver->beh->AUTO_INCREMENT_TYPE.isEmpty())
v += m_driver->beh->AUTO_INCREMENT_TYPE;
else
v += m_driver->sqlTypeName(field->type(), field->precision());
if (field->isUnsigned())
v += (" " + m_driver->beh->UNSIGNED_TYPE_KEYWORD);
if (field->isFPNumericType() && field->precision()>0) {
if (field->scale()>0)
v += QString::fromLatin1("(%1,%2)").arg(field->precision()).arg(field->scale());
else
v += QString::fromLatin1("(%1)").arg(field->precision());
}
else if (field->type()==Field::Text && field->length()>0)
v += QString::fromLatin1("(%1)").arg(field->length());
if (autoinc)
v += (" " +
(pk ? m_driver->beh->AUTO_INCREMENT_PK_FIELD_OPTION : m_driver->beh->AUTO_INCREMENT_FIELD_OPTION));
else
//TODO: here is automatically a single-field key created
if (pk)
v += " PRIMARY KEY";
if (!pk && field->isUniqueKey())
v += " UNIQUE";
///@todo IS this ok for all engines?: if (!autoinc && !field->isPrimaryKey() && field->isNotNull())
if (!autoinc && !pk && field->isNotNull())
v += " NOT NULL"; //only add not null option if no autocommit is set
if (field->defaultValue().isValid()) {
QString valToSQL( m_driver->valueToSQL( field, field->defaultValue() ) );
if (!valToSQL.isEmpty()) //for sanity
v += QString::fromLatin1(" DEFAULT ") + valToSQL;
}
}
sql += v;
}
sql += ")";
return sql;
}
//yeah, it is very efficient:
#define C_A(a) , const QVariant& c ## a
#define V_A0 m_driver->valueToSQL( tableSchema.field(0), c0 )
#define V_A(a) +","+m_driver->valueToSQL( \
tableSchema.field(a) ? tableSchema.field(a)->type() : Field::Text, c ## a )
// KexiDBDbg << "******** " << QString("INSERT INTO ") +
// escapeIdentifier(tableSchema.name()) +
// " VALUES (" + vals + ")" <<endl;
#define C_INS_REC(args, vals) \
bool Connection::insertRecord(KexiDB::TableSchema &tableSchema args) {\
return executeSQL( \
QString("INSERT INTO ") + escapeIdentifier(tableSchema.name()) + " VALUES (" + vals + ")" \
); \
}
#define C_INS_REC_ALL \
C_INS_REC( C_A(0), V_A0 ) \
C_INS_REC( C_A(0) C_A(1), V_A0 V_A(1) ) \
C_INS_REC( C_A(0) C_A(1) C_A(2), V_A0 V_A(1) V_A(2) ) \
C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3), V_A0 V_A(1) V_A(2) V_A(3) ) \
C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) ) \
C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) ) \
C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) ) \
C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6) C_A(7), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) V_A(7) )
C_INS_REC_ALL
#undef V_A0
#undef V_A
#undef C_INS_REC
#define V_A0 value += m_driver->valueToSQL( flist->first(), c0 );
#define V_A( a ) value += ("," + m_driver->valueToSQL( flist->next(), c ## a ));
//#define V_ALAST( a ) valueToSQL( flist->last(), c ## a )
#define C_INS_REC(args, vals) \
bool Connection::insertRecord(FieldList& fields args) \
{ \
QString value; \
Field::List *flist = fields.fields(); \
vals \
return executeSQL( \
QString("INSERT INTO ") + \
((fields.fields()->first() && fields.fields()->first()->table()) ? \
escapeIdentifier(fields.fields()->first()->table()->name()) : \
"??") \
+ "(" + fields.sqlFieldsList(m_driver) + ") VALUES (" + value + ")" \
); \
}
C_INS_REC_ALL
#undef C_A
#undef V_A
#undef V_ALAST
#undef C_INS_REC
#undef C_INS_REC_ALL
bool Connection::insertRecord(TableSchema &tableSchema, QValueList<QVariant>& values)
{
// Each SQL identifier needs to be escaped in the generated query.
Field::List *fields = tableSchema.fields();
Field *f = fields->first();
// QString s_val;
// s_val.reserve(4096);
m_sql = QString::null;
QValueList<QVariant>::ConstIterator it = values.constBegin();
// int i=0;
while (f && (it!=values.end())) {
if (m_sql.isEmpty())
m_sql = QString("INSERT INTO ") +
escapeIdentifier(tableSchema.name()) +
" VALUES (";
else
m_sql += ",";
m_sql += m_driver->valueToSQL( f, *it );
// KexiDBDbg << "val" << i++ << ": " << m_driver->valueToSQL( f, *it ) << endl;
++it;
f=fields->next();
}
m_sql += ")";
// KexiDBDbg<<"******** "<< m_sql << endl;
return executeSQL(m_sql);
}
bool Connection::insertRecord(FieldList& fields, QValueList<QVariant>& values)
{
// Each SQL identifier needs to be escaped in the generated query.
Field::List *flist = fields.fields();
Field *f = flist->first();
if (!f)
return false;
// QString s_val;
// s_val.reserve(4096);
m_sql = QString::null;
QValueList<QVariant>::ConstIterator it = values.constBegin();
// int i=0;
while (f && (it!=values.constEnd())) {
if (m_sql.isEmpty())
m_sql = QString("INSERT INTO ") +
escapeIdentifier(flist->first()->table()->name()) + "(" +
fields.sqlFieldsList(m_driver) + ") VALUES (";
else
m_sql += ",";
m_sql += m_driver->valueToSQL( f, *it );
// KexiDBDbg << "val" << i++ << ": " << m_driver->valueToSQL( f, *it ) << endl;
++it;
f=flist->next();
}
m_sql += ")";
return executeSQL(m_sql);
}
bool Connection::executeSQL( const QString& statement )
{
m_sql = statement; //remember for error handling
if (!drv_executeSQL( m_sql )) {
m_errMsg = QString::null; //clear as this could be most probably jsut "Unknown error" string.
m_errorSql = statement;
setError(this, ERR_SQL_EXECUTION_ERROR, i18n("Error while executing SQL statement."));
return false;
}
return true;
}
QString Connection::selectStatement( KexiDB::QuerySchema& querySchema,
const QValueList<QVariant>& params,
const SelectStatementOptions& options) const
{
//"SELECT FROM ..." is theoretically allowed "
//if (querySchema.fieldCount()<1)
// return QString::null;
// Each SQL identifier needs to be escaped in the generated query.
if (!querySchema.statement().isEmpty())
return querySchema.statement();
//! @todo looking at singleTable is visually nice but a field name can conflict
//! with function or variable name...
Field *f;
uint number = 0;
bool singleTable = querySchema.tables()->count() <= 1;
if (singleTable) {
//make sure we will have single table:
for (Field::ListIterator it = querySchema.fieldsIterator(); (f = it.current()); ++it, number++) {
if (querySchema.isColumnVisible(number) && f->table() && f->table()->lookupFieldSchema( *f )) {
//uups, no, there's at least one left join
singleTable = false;
break;
}
}
}
QString sql; //final sql string
sql.reserve(4096);
//unused QString s_from_additional; //additional tables list needed for lookup fields
QString s_additional_joins; //additional joins needed for lookup fields
QString s_additional_fields; //additional fields to append to the fields list
uint internalUniqueTableAliasNumber = 0; //used to build internalUniqueTableAliases
uint internalUniqueQueryAliasNumber = 0; //used to build internalUniqueQueryAliases
number = 0;
QPtrList<QuerySchema> subqueries_for_lookup_data; // subqueries will be added to FROM section
QString kexidb_subquery_prefix("__kexidb_subquery_");
for (Field::ListIterator it = querySchema.fieldsIterator(); (f = it.current()); ++it, number++) {
if (querySchema.isColumnVisible(number)) {
if (!sql.isEmpty())
sql += QString::fromLatin1(", ");
if (f->isQueryAsterisk()) {
if (!singleTable && static_cast<QueryAsterisk*>(f)->isSingleTableAsterisk()) //single-table *
sql += escapeIdentifier(f->table()->name(), options.identifierEscaping) +
QString::fromLatin1(".*");
else //all-tables * (or simplified table.* when there's only one table)
sql += QString::fromLatin1("*");
}
else {
if (f->isExpression()) {
sql += f->expression()->toString();
}
else {
if (!f->table()) //sanity check
return QString::null;
QString tableName;
int tablePosition = querySchema.tableBoundToColumn(number);
if (tablePosition>=0)
tableName = querySchema.tableAlias(tablePosition);
if (tableName.isEmpty())
tableName = f->table()->name();
if (!singleTable) {
sql += (escapeIdentifier(tableName, options.identifierEscaping) + ".");
}
sql += escapeIdentifier(f->name(), options.identifierEscaping);
}
QString aliasString = QString(querySchema.columnAlias(number));
if (!aliasString.isEmpty())
sql += (QString::fromLatin1(" AS ") + aliasString);
//! @todo add option that allows to omit "AS" keyword
}
LookupFieldSchema *lookupFieldSchema = (options.addVisibleLookupColumns && f->table())
? f->table()->lookupFieldSchema( *f ) : 0;
if (lookupFieldSchema && lookupFieldSchema->boundColumn()>=0) {
// Lookup field schema found
// Now we also need to fetch "visible" value from the lookup table, not only the value of binding.
// -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken)
// "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField"
LookupFieldSchema::RowSource& rowSource = lookupFieldSchema->rowSource();
if (rowSource.type()==LookupFieldSchema::RowSource::Table) {
TableSchema *lookupTable = querySchema.connection()->tableSchema( rowSource.name() );
FieldList* visibleColumns = 0;
Field *boundField = 0;
if (lookupTable
&& (uint)lookupFieldSchema->boundColumn() < lookupTable->fieldCount()
&& (visibleColumns = lookupTable->subList( lookupFieldSchema->visibleColumns() ))
&& (boundField = lookupTable->field( lookupFieldSchema->boundColumn() )))
{
//add LEFT OUTER JOIN
if (!s_additional_joins.isEmpty())
s_additional_joins += QString::fromLatin1(" ");
QString internalUniqueTableAlias( QString("__kexidb_") + lookupTable->name() + "_"
+ QString::number(internalUniqueTableAliasNumber++) );
s_additional_joins += QString("LEFT OUTER JOIN %1 AS %2 ON %3.%4=%5.%6")
.arg(escapeIdentifier(lookupTable->name(), options.identifierEscaping))
.arg(internalUniqueTableAlias)
.arg(escapeIdentifier(f->table()->name(), options.identifierEscaping))
.arg(escapeIdentifier(f->name(), options.identifierEscaping))
.arg(internalUniqueTableAlias)
.arg(escapeIdentifier(boundField->name(), options.identifierEscaping));
//add visibleField to the list of SELECTed fields //if it is not yet present there
//not needed if (!querySchema.findTableField( visibleField->table()->name()+"."+visibleField->name() )) {
#if 0
if (!querySchema.table( visibleField->table()->name() )) {
/* not true
//table should be added after FROM
if (!s_from_additional.isEmpty())
s_from_additional += QString::fromLatin1(", ");
s_from_additional += escapeIdentifier(visibleField->table()->name(), options.identifierEscaping);
*/
}
#endif
if (!s_additional_fields.isEmpty())
s_additional_fields += QString::fromLatin1(", ");
// s_additional_fields += (internalUniqueTableAlias + "." //escapeIdentifier(visibleField->table()->name(), options.identifierEscaping) + "."
// escapeIdentifier(visibleField->name(), options.identifierEscaping));
//! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?"
//! @todo Add possibility for joining the values at client side.
s_additional_fields += visibleColumns->sqlFieldsList(
driver(), " || ' ' || ", internalUniqueTableAlias, options.identifierEscaping);
}
delete visibleColumns;
}
else if (rowSource.type()==LookupFieldSchema::RowSource::Query) {
QuerySchema *lookupQuery = querySchema.connection()->querySchema( rowSource.name() );
if (!lookupQuery) {
KexiDBWarn << "Connection::selectStatement(): !lookupQuery" << endl;
return QString::null;
}
const QueryColumnInfo::Vector fieldsExpanded( lookupQuery->fieldsExpanded() );
if ((uint)lookupFieldSchema->boundColumn() >= fieldsExpanded.count()) {
KexiDBWarn << "Connection::selectStatement(): (uint)lookupFieldSchema->boundColumn() >= fieldsExpanded.count()" << endl;
return QString::null;
}
QueryColumnInfo *boundColumnInfo = fieldsExpanded.at( lookupFieldSchema->boundColumn() );
if (!boundColumnInfo) {
KexiDBWarn << "Connection::selectStatement(): !boundColumnInfo" << endl;
return QString::null;
}
Field *boundField = boundColumnInfo->field;
if (!boundField) {
KexiDBWarn << "Connection::selectStatement(): !boundField" << endl;
return QString::null;
}
//add LEFT OUTER JOIN
if (!s_additional_joins.isEmpty())
s_additional_joins += QString::fromLatin1(" ");
QString internalUniqueQueryAlias(
kexidb_subquery_prefix + lookupQuery->name() + "_"
+ QString::number(internalUniqueQueryAliasNumber++) );
s_additional_joins += QString("LEFT OUTER JOIN (%1) AS %2 ON %3.%4=%5.%6")
.arg(selectStatement( *lookupQuery, params, options ))
.arg(internalUniqueQueryAlias)
.arg(escapeIdentifier(f->table()->name(), options.identifierEscaping))
.arg(escapeIdentifier(f->name(), options.identifierEscaping))
.arg(internalUniqueQueryAlias)
.arg(escapeIdentifier(boundColumnInfo->aliasOrName(), options.identifierEscaping));
if (!s_additional_fields.isEmpty())
s_additional_fields += QString::fromLatin1(", ");
const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() );
QString expression;
foreach (QValueList<uint>::ConstIterator, visibleColumnsIt, visibleColumns) {
//! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?"
//! @todo Add possibility for joining the values at client side.
if (fieldsExpanded.count() <= (*visibleColumnsIt)) {
KexiDBWarn << "Connection::selectStatement(): fieldsExpanded.count() <= (*visibleColumnsIt) : "
<< fieldsExpanded.count() << " <= " << *visibleColumnsIt << endl;
return QString::null;
}
if (!expression.isEmpty())
expression += " || ' ' || ";
expression += (internalUniqueQueryAlias + "." +
escapeIdentifier(fieldsExpanded[*visibleColumnsIt]->aliasOrName(),
options.identifierEscaping));
}
s_additional_fields += expression;
//subqueries_for_lookup_data.append(lookupQuery);
}
else {
KexiDBWarn << "Connection::selectStatement(): unsupported row source type "
<< rowSource.typeName() << endl;
return QString();
}
}
}
}
//add lookup fields
if (!s_additional_fields.isEmpty())
sql += (QString::fromLatin1(", ") + s_additional_fields);
if (options.alsoRetrieveROWID) { //append rowid column
QString s;
if (!sql.isEmpty())
s = QString::fromLatin1(", ");
if (querySchema.masterTable())
s += (escapeIdentifier(querySchema.masterTable()->name())+".");
s += m_driver->beh->ROW_ID_FIELD_NAME;
sql += s;
}
sql.prepend("SELECT ");
TableSchema::List* tables = querySchema.tables();
if ((tables && !tables->isEmpty()) || !subqueries_for_lookup_data.isEmpty()) {
sql += QString::fromLatin1(" FROM ");
QString s_from;
if (tables) {
TableSchema *table;
number = 0;
for (TableSchema::ListIterator it(*tables); (table = it.current());
++it, number++)
{
if (!s_from.isEmpty())
s_from += QString::fromLatin1(", ");
s_from += escapeIdentifier(table->name(), options.identifierEscaping);
QString aliasString = QString(querySchema.tableAlias(number));
if (!aliasString.isEmpty())
s_from += (QString::fromLatin1(" AS ") + aliasString);
}
/*unused if (!s_from_additional.isEmpty()) {//additional tables list needed for lookup fields
if (!s_from.isEmpty())
s_from += QString::fromLatin1(", ");
s_from += s_from_additional;
}*/
}
// add subqueries for lookup data
uint subqueries_for_lookup_data_counter = 0;
for (QPtrListIterator<QuerySchema> it(subqueries_for_lookup_data);
subqueries_for_lookup_data.current(); ++it, subqueries_for_lookup_data_counter++)
{
if (!s_from.isEmpty())
s_from += QString::fromLatin1(", ");
s_from += QString::fromLatin1("(");
s_from += selectStatement( *it.current(), params, options );
s_from += QString::fromLatin1(") AS %1%2")
.arg(kexidb_subquery_prefix).arg(subqueries_for_lookup_data_counter);
}
sql += s_from;
}
QString s_where;
s_where.reserve(4096);
//JOINS
if (!s_additional_joins.isEmpty()) {
sql += QString::fromLatin1(" ") + s_additional_joins + QString::fromLatin1(" ");
}
//@todo: we're using WHERE for joins now; use INNER/LEFT/RIGHT JOIN later
//WHERE
Relationship *rel;
bool wasWhere = false; //for later use
for (Relationship::ListIterator it(*querySchema.relationships()); (rel = it.current()); ++it) {
if (s_where.isEmpty()) {
wasWhere = true;
}
else
s_where += QString::fromLatin1(" AND ");
Field::Pair *pair;
QString s_where_sub;
for (QPtrListIterator<Field::Pair> p_it(*rel->fieldPairs()); (pair = p_it.current()); ++p_it) {
if (!s_where_sub.isEmpty())
s_where_sub += QString::fromLatin1(" AND ");
s_where_sub += (
escapeIdentifier(pair->first->table()->name(), options.identifierEscaping) +
QString::fromLatin1(".") +
escapeIdentifier(pair->first->name(), options.identifierEscaping) +
QString::fromLatin1(" = ") +
escapeIdentifier(pair->second->table()->name(), options.identifierEscaping) +
QString::fromLatin1(".") +
escapeIdentifier(pair->second->name(), options.identifierEscaping));
}
if (rel->fieldPairs()->count()>1) {
s_where_sub.prepend("(");
s_where_sub += QString::fromLatin1(")");
}
s_where += s_where_sub;
}
//EXPLICITLY SPECIFIED WHERE EXPRESSION
if (querySchema.whereExpression()) {
QuerySchemaParameterValueListIterator paramValuesIt(*m_driver, params);
QuerySchemaParameterValueListIterator *paramValuesItPtr = params.isEmpty() ? 0 : &paramValuesIt;
if (wasWhere) {
//TODO: () are not always needed
s_where = "(" + s_where + ") AND (" + querySchema.whereExpression()->toString(paramValuesItPtr) + ")";
}
else {
s_where = querySchema.whereExpression()->toString(paramValuesItPtr);
}
}
if (!s_where.isEmpty())
sql += QString::fromLatin1(" WHERE ") + s_where;
//! \todo (js) add other sql parts
//(use wasWhere here)
// ORDER BY
QString orderByString(
querySchema.orderByColumnList().toSQLString(!singleTable/*includeTableName*/,
driver(), options.identifierEscaping) );
const QValueVector<int> pkeyFieldsOrder( querySchema.pkeyFieldsOrder() );
if (orderByString.isEmpty() && !pkeyFieldsOrder.isEmpty()) {
//add automatic ORDER BY if there is no explicity defined (especially helps when there are complex JOINs)
OrderByColumnList automaticPKOrderBy;
const QueryColumnInfo::Vector fieldsExpanded( querySchema.fieldsExpanded() );
foreach (QValueVector<int>::ConstIterator, it, pkeyFieldsOrder) {
if ((*it) < 0) // no field mentioned in this query
continue;
if ((*it) >= (int)fieldsExpanded.count()) {
KexiDBWarn << "Connection::selectStatement(): ORDER BY: (*it) >= fieldsExpanded.count() - "
<< (*it) << " >= " << fieldsExpanded.count() << endl;
continue;
}
QueryColumnInfo *ci = fieldsExpanded[ *it ];
automaticPKOrderBy.appendColumn( *ci );
}
orderByString = automaticPKOrderBy.toSQLString(!singleTable/*includeTableName*/,
driver(), options.identifierEscaping);
}
if (!orderByString.isEmpty())
sql += (" ORDER BY " + orderByString);
//KexiDBDbg << sql << endl;
return sql;
}
QString Connection::selectStatement( KexiDB::TableSchema& tableSchema,
const SelectStatementOptions& options) const
{
return selectStatement( *tableSchema.query(), options );
}
Field* Connection::findSystemFieldName(KexiDB::FieldList* fieldlist)
{
Field *f = fieldlist->fields()->first();
while (f) {
if (m_driver->isSystemFieldName( f->name() ))
return f;
f = fieldlist->fields()->next();
}
return 0;
}
Q_ULLONG Connection::lastInsertedAutoIncValue(const QString& aiFieldName, const QString& tableName,
Q_ULLONG* ROWID)
{
Q_ULLONG row_id = drv_lastInsertRowID();
if (ROWID)
*ROWID = row_id;
if (m_driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE) {
return row_id;
}
RowData rdata;
if (row_id<=0 || true!=querySingleRecord(
QString::fromLatin1("SELECT ") + tableName + QString::fromLatin1(".") + aiFieldName + QString::fromLatin1(" FROM ") + tableName
+ QString::fromLatin1(" WHERE ") + m_driver->beh->ROW_ID_FIELD_NAME + QString::fromLatin1("=") + QString::number(row_id), rdata))
{
// KexiDBDbg << "Connection::lastInsertedAutoIncValue(): row_id<=0 || true!=querySingleRecord()" << endl;
return (Q_ULLONG)-1; //ULL;
}
return rdata[0].toULongLong();
}
Q_ULLONG Connection::lastInsertedAutoIncValue(const QString& aiFieldName,
const KexiDB::TableSchema& table, Q_ULLONG* ROWID)
{
return lastInsertedAutoIncValue(aiFieldName,table.name(), ROWID);
}
//! Creates a Field list for kexi__fields, for sanity. Used by createTable()
static FieldList* createFieldListForKexi__Fields(TableSchema *kexi__fieldsSchema)
{
if (!kexi__fieldsSchema)
return 0;
return kexi__fieldsSchema->subList(
"t_id",
"f_type",
"f_name",
"f_length",
"f_precision",
"f_constraints",
"f_options",
"f_default",
"f_order",
"f_caption",
"f_help"
);
}
//! builds a list of values for field's \a f properties. Used by createTable().
void buildValuesForKexi__Fields(QValueList<QVariant>& vals, Field* f)
{
vals.clear();
vals
<< QVariant(f->table()->id())
<< QVariant(f->type())
<< QVariant(f->name())
<< QVariant(f->isFPNumericType() ? f->scale() : f->length())
<< QVariant(f->isFPNumericType() ? f->precision() : 0)
<< QVariant(f->constraints())
<< QVariant(f->options())
// KexiDB::variantToString() is needed here because the value can be of any QVariant type,
// depending on f->type()
<< (f->defaultValue().isNull()
? QVariant() : QVariant(KexiDB::variantToString( f->defaultValue() )))
<< QVariant(f->order())
<< QVariant(f->caption())
<< QVariant(f->description());
}
bool Connection::storeMainFieldSchema(Field *field)
{
if (!field || !field->table())
return false;
FieldList *fl = createFieldListForKexi__Fields(d->tables_byname["kexi__fields"]);
if (!fl)
return false;
QValueList<QVariant> vals;
buildValuesForKexi__Fields(vals, field);
QValueList<QVariant>::ConstIterator valsIt = vals.constBegin();
Field *f;
bool first = true;
QString sql = "UPDATE kexi__fields SET ";
for (Field::ListIterator it( fl->fieldsIterator() ); (f = it.current()); ++it, ++valsIt) {
sql.append( (first ? QString::null : QString(", ")) +
f->name() + "=" + m_driver->valueToSQL( f, *valsIt ) );
if (first)
first = false;
}
delete fl;
sql.append(QString(" WHERE t_id=") + QString::number( field->table()->id() )
+ " AND f_name=" + m_driver->valueToSQL( Field::Text, field->name() ) );
return executeSQL( sql );
}
#define createTable_ERR \
{ KexiDBDbg << "Connection::createTable(): ERROR!" <<endl; \
setError(this, i18n("Creating table failed.")); \
rollbackAutoCommitTransaction(tg.transaction()); \
return false; }
//setError( errorNum(), i18n("Creating table failed.") + " " + errorMsg());
//! Creates a table according to the given schema
/*! Creates a table according to the given TableSchema, adding the table and
column definitions to kexi__* tables. Checks that a database is in use,
that the table name is not that of a system table, and that the schema
defines at least one column.
If the table exists, and replaceExisting is true, the table is replaced.
Otherwise, the table is not replaced.
*/
bool Connection::createTable( KexiDB::TableSchema* tableSchema, bool replaceExisting )
{
if (!tableSchema || !checkIsDatabaseUsed())
return false;
//check if there are any fields
if (tableSchema->fieldCount()<1) {
clearError();
setError(ERR_CANNOT_CREATE_EMPTY_OBJECT, i18n("Cannot create table without fields."));
return false;
}
const bool internalTable = dynamic_cast<InternalTableSchema*>(tableSchema);
const QString &tableName = tableSchema->name().lower();
if (!internalTable) {
if (m_driver->isSystemObjectName( tableName )) {
clearError();
setError(ERR_SYSTEM_NAME_RESERVED, i18n("System name \"%1\" cannot be used as table name.")
.arg(tableSchema->name()));
return false;
}
Field *sys_field = findSystemFieldName(tableSchema);
if (sys_field) {
clearError();
setError(ERR_SYSTEM_NAME_RESERVED,
i18n("System name \"%1\" cannot be used as one of fields in \"%2\" table.")
.arg(sys_field->name()).arg(tableName));
return false;
}
}
bool previousSchemaStillKept = false;
KexiDB::TableSchema *existingTable = 0;
if (replaceExisting) {
//get previous table (do not retrieve, though)
existingTable = d->tables_byname[tableName];
if (existingTable) {
if (existingTable == tableSchema) {
clearError();
setError(ERR_OBJECT_EXISTS,
i18n("Could not create the same table \"%1\" twice.").arg(tableSchema->name()) );
return false;
}
//TODO(js): update any structure (e.g. queries) that depend on this table!
if (existingTable->id()>0)
tableSchema->m_id = existingTable->id(); //copy id from existing table
previousSchemaStillKept = true;
if (!dropTable( existingTable, false /*alsoRemoveSchema*/ ))
return false;
}
}
else {
if (this->tableSchema( tableSchema->name() ) != 0) {
clearError();
setError(ERR_OBJECT_EXISTS, i18n("Table \"%1\" already exists.").arg(tableSchema->name()) );
return false;
}
}
/* if (replaceExisting) {
//get previous table (do not retrieve, though)
KexiDB::TableSchema *existingTable = d->tables_byname.take(name);
if (oldTable) {
}*/
TransactionGuard tg;
if (!beginAutoCommitTransaction(tg))
return false;
if (!drv_createTable(*tableSchema))
createTable_ERR;
//add schema data to kexi__* tables
if (!internalTable) {
//update kexi__objects
if (!storeObjectSchemaData( *tableSchema, true ))
createTable_ERR;
TableSchema *ts = d->tables_byname["kexi__fields"];
if (!ts)
return false;
//for sanity: remove field info (if any) for this table id
if (!KexiDB::deleteRow(*this, ts, "t_id", tableSchema->id()))
return false;
FieldList *fl = createFieldListForKexi__Fields(d->tables_byname["kexi__fields"]);
if (!fl)
return false;
// int order = 0;
Field *f;
for (Field::ListIterator it( *tableSchema->fields() ); (f = it.current()); ++it/*, order++*/) {
QValueList<QVariant> vals;
buildValuesForKexi__Fields(vals, f);
if (!insertRecord(*fl, vals ))
createTable_ERR;
}
delete fl;
if (!storeExtendedTableSchemaData(*tableSchema))
createTable_ERR;
}
//finally:
/* if (replaceExisting) {
if (existingTable) {
d->tables.take(existingTable->id());
delete existingTable;
}
}*/
bool res = commitAutoCommitTransaction(tg.transaction());
if (res) {
if (internalTable) {
//insert the internal table into structures
insertInternalTableSchema(tableSchema);
}
else {
if (previousSchemaStillKept) {
//remove previous table schema
removeTableSchemaInternal(tableSchema);
}
//store one schema object locally:
d->tables.insert(tableSchema->id(), tableSchema);
d->tables_byname.insert(tableSchema->name().lower(), tableSchema);
}
//ok, this table is not created by the connection
tableSchema->m_conn = this;
}
return res;
}
void Connection::removeTableSchemaInternal(TableSchema *tableSchema)
{
d->tables_byname.remove(tableSchema->name());
d->tables.remove(tableSchema->id());
}
bool Connection::removeObject( uint objId )
{
clearError();
//remove table schema from kexi__* tables
if (!KexiDB::deleteRow(*this, d->tables_byname["kexi__objects"], "o_id", objId) //schema entry
|| !KexiDB::deleteRow(*this, d->tables_byname["kexi__objectdata"], "o_id", objId)) {//data blocks
setError(ERR_DELETE_SERVER_ERROR, i18n("Could not remove object's data."));
return false;
}
return true;
}
bool Connection::drv_dropTable( const QString& name )
{
m_sql = "DROP TABLE " + escapeIdentifier(name);
return executeSQL(m_sql);
}
//! Drops a table corresponding to the name in the given schema
/*! Drops a table according to the name given by the TableSchema, removing the
table and column definitions to kexi__* tables. Checks first that the
table is not a system table.
TODO: Should check that a database is currently in use? (c.f. createTable)
*/
tristate Connection::dropTable( KexiDB::TableSchema* tableSchema )
{
return dropTable( tableSchema, true );
}
tristate Connection::dropTable( KexiDB::TableSchema* tableSchema, bool alsoRemoveSchema)
{
// Each SQL identifier needs to be escaped in the generated query.
clearError();
if (!tableSchema)
return false;
QString errmsg(i18n("Table \"%1\" cannot be removed.\n"));
//be sure that we handle the correct TableSchema object:
if (tableSchema->id() < 0
|| this->tableSchema(tableSchema->name())!=tableSchema
|| this->tableSchema(tableSchema->id())!=tableSchema)
{
setError(ERR_OBJECT_NOT_FOUND, errmsg.arg(tableSchema->name())
+i18n("Unexpected name or identifier."));
return false;
}
tristate res = closeAllTableSchemaChangeListeners(*tableSchema);
if (true!=res)
return res;
//sanity checks:
if (m_driver->isSystemObjectName( tableSchema->name() )) {
setError(ERR_SYSTEM_NAME_RESERVED, errmsg.arg(tableSchema->name()) + d->strItIsASystemObject());
return false;
}
TransactionGuard tg;
if (!beginAutoCommitTransaction(tg))
return false;
//for sanity we're checking if this table exists physically
if (drv_containsTable(tableSchema->name())) {
if (!drv_dropTable(tableSchema->name()))
return false;
}
TableSchema *ts = d->tables_byname["kexi__fields"];
if (!KexiDB::deleteRow(*this, ts, "t_id", tableSchema->id())) //field entries
return false;
//remove table schema from kexi__objects table
if (!removeObject( tableSchema->id() )) {
return false;
}
if (alsoRemoveSchema) {
//! \todo js: update any structure (e.g. queries) that depend on this table!
tristate res = removeDataBlock( tableSchema->id(), "extended_schema");
if (!res)
return false;
removeTableSchemaInternal(tableSchema);
}
return commitAutoCommitTransaction(tg.transaction());
}
tristate Connection::dropTable( const QString& table )
{
clearError();
TableSchema* ts = tableSchema( table );
if (!ts) {
setError(ERR_OBJECT_NOT_FOUND, i18n("Table \"%1\" does not exist.")
.arg(table));
return false;
}
return dropTable(ts);
}
tristate Connection::alterTable( TableSchema& tableSchema, TableSchema& newTableSchema )
{
clearError();
tristate res = closeAllTableSchemaChangeListeners(tableSchema);
if (true!=res)
return res;
if (&tableSchema == &newTableSchema) {
setError(ERR_OBJECT_THE_SAME, i18n("Could not alter table \"%1\" using the same table.")
.arg(tableSchema.name()));
return false;
}
//TODO(js): implement real altering
//TODO(js): update any structure (e.g. query) that depend on this table!
bool ok, empty;
#if 0//TODO ucomment:
empty = isEmpty( tableSchema, ok ) && ok;
#else
empty = true;
#endif
if (empty) {
ok = createTable(&newTableSchema, true/*replace*/);
}
return ok;
}
bool Connection::alterTableName(TableSchema& tableSchema, const QString& newName, bool replace)
{
clearError();
if (&tableSchema!=d->tables[tableSchema.id()]) {
setError(ERR_OBJECT_NOT_FOUND, i18n("Unknown table \"%1\"").arg(tableSchema.name()));
return false;
}
if (newName.isEmpty() || !KexiUtils::isIdentifier(newName)) {
setError(ERR_INVALID_IDENTIFIER, i18n("Invalid table name \"%1\"").arg(newName));
return false;
}
const QString oldTableName = tableSchema.name();
const QString newTableName = newName.lower().stripWhiteSpace();
if (oldTableName.lower().stripWhiteSpace() == newTableName) {
setError(ERR_OBJECT_THE_SAME, i18n("Could rename table \"%1\" using the same name.")
.arg(newTableName));
return false;
}
//TODO: alter table name for server DB backends!
//TODO: what about objects (queries/forms) that use old name?
//TODO
TableSchema *tableToReplace = this->tableSchema( newName );
const bool destTableExists = tableToReplace != 0;
const int origID = destTableExists ? tableToReplace->id() : -1; //will be reused in the new table
if (!replace && destTableExists) {
setError(ERR_OBJECT_EXISTS,
i18n("Could not rename table \"%1\" to \"%2\". Table \"%3\" already exists.")
.arg(tableSchema.name()).arg(newName).arg(newName));
return false;
}
//helper:
#define alterTableName_ERR \
tableSchema.setName(oldTableName) //restore old name
TransactionGuard tg;
if (!beginAutoCommitTransaction(tg))
return false;
// drop the table replaced (with schema)
if (destTableExists) {
if (!replace) {
return false;
}
if (!dropTable( newName )) {
return false;
}
// the new table owns the previous table's id:
if (!executeSQL(QString::fromLatin1("UPDATE kexi__objects SET o_id=%1 WHERE o_id=%2 AND o_type=%3")
.arg(origID).arg(tableSchema.id()).arg((int)TableObjectType)))
{
return false;
}
if (!executeSQL(QString::fromLatin1("UPDATE kexi__fields SET t_id=%1 WHERE t_id=%2")
.arg(origID).arg(tableSchema.id())))
{
return false;
}
d->tables.take(tableSchema.id());
d->tables.insert(origID, &tableSchema);
//maintain table ID
tableSchema.m_id = origID;
}
if (!drv_alterTableName(tableSchema, newTableName)) {
alterTableName_ERR;
return false;
}
// Update kexi__objects
//TODO
if (!executeSQL(QString::fromLatin1("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2")
.arg(m_driver->escapeString(tableSchema.name())).arg(tableSchema.id())))
{
alterTableName_ERR;
return false;
}
//TODO what about caption?
//restore old name: it will be changed soon!
tableSchema.setName(oldTableName);
if (!commitAutoCommitTransaction(tg.transaction())) {
alterTableName_ERR;
return false;
}
//update tableSchema:
d->tables_byname.take(tableSchema.name());
tableSchema.setName(newTableName);
d->tables_byname.insert(tableSchema.name(), &tableSchema);
return true;
}
bool Connection::drv_alterTableName(TableSchema& tableSchema, const QString& newName)
{
const QString oldTableName = tableSchema.name();
tableSchema.setName(newName);
if (!executeSQL(QString::fromLatin1("ALTER TABLE %1 RENAME TO %2")
.arg(escapeIdentifier(oldTableName)).arg(escapeIdentifier(newName))))
{
tableSchema.setName(oldTableName); //restore old name
return false;
}
return true;
}
bool Connection::dropQuery( KexiDB::QuerySchema* querySchema )
{
clearError();
if (!querySchema)
return false;
TransactionGuard tg;
if (!beginAutoCommitTransaction(tg))
return false;
/* TableSchema *ts = d->tables_byname["kexi__querydata"];
if (!KexiDB::deleteRow(*this, ts, "q_id", querySchema->id()))
return false;
ts = d->tables_byname["kexi__queryfields"];
if (!KexiDB::deleteRow(*this, ts, "q_id", querySchema->id()))
return false;
ts = d->tables_byname["kexi__querytables"];
if (!KexiDB::deleteRow(*this, ts, "q_id", querySchema->id()))
return false;*/
//remove query schema from kexi__objects table
if (!removeObject( querySchema->id() )) {
return false;
}
//TODO(js): update any structure that depend on this table!
d->queries_byname.remove(querySchema->name());
d->queries.remove(querySchema->id());
return commitAutoCommitTransaction(tg.transaction());
}
bool Connection::dropQuery( const QString& query )
{
clearError();
QuerySchema* qs = querySchema( query );
if (!qs) {
setError(ERR_OBJECT_NOT_FOUND, i18n("Query \"%1\" does not exist.")
.arg(query));
return false;
}
return dropQuery(qs);
}
bool Connection::drv_createTable( const KexiDB::TableSchema& tableSchema )
{
m_sql = createTableStatement(tableSchema);
KexiDBDbg<<"******** "<<m_sql<<endl;
return executeSQL(m_sql);
}
bool Connection::drv_createTable( const QString& tableSchemaName )
{
TableSchema *ts = d->tables_byname[tableSchemaName];
if (!ts)
return false;
return drv_createTable(*ts);
}
bool Connection::beginAutoCommitTransaction(TransactionGuard &tg)
{
if ((m_driver->d->features & Driver::IgnoreTransactions)
|| !d->autoCommit)
{
tg.setTransaction( Transaction() );
return true;
}
// commit current transaction (if present) for drivers
// that allow single transaction per connection
if (m_driver->d->features & Driver::SingleTransactions) {
if (d->default_trans_started_inside) //only commit internally started transaction
if (!commitTransaction(d->default_trans, true)) {
tg.setTransaction( Transaction() );
return false; //we have a real error
}
d->default_trans_started_inside = d->default_trans.isNull();
if (!d->default_trans_started_inside) {
tg.setTransaction( d->default_trans );
tg.doNothing();
return true; //reuse externally started transaction
}
}
else if (!(m_driver->d->features & Driver::MultipleTransactions)) {
tg.setTransaction( Transaction() );
return true; //no trans. supported at all - just return
}
tg.setTransaction( beginTransaction() );
return !error();
}
bool Connection::commitAutoCommitTransaction(const Transaction& trans)
{
if (m_driver->d->features & Driver::IgnoreTransactions)
return true;
if (trans.isNull() || !m_driver->transactionsSupported())
return true;
if (m_driver->d->features & Driver::SingleTransactions) {
if (!d->default_trans_started_inside) //only commit internally started transaction
return true; //give up
}
return commitTransaction(trans, true);
}
bool Connection::rollbackAutoCommitTransaction(const Transaction& trans)
{
if (trans.isNull() || !m_driver->transactionsSupported())
return true;
return rollbackTransaction(trans);
}
#define SET_ERR_TRANS_NOT_SUPP \
{ setError(ERR_UNSUPPORTED_DRV_FEATURE, \
i18n("Transactions are not supported for \"%1\" driver.").arg(m_driver->name() )); }
#define SET_BEGIN_TR_ERROR \
{ if (!error()) \
setError(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, i18n("Begin transaction failed")); }
Transaction Connection::beginTransaction()
{
if (!checkIsDatabaseUsed())
return Transaction::null;
Transaction trans;
if (m_driver->d->features & Driver::IgnoreTransactions) {
//we're creating dummy transaction data here,
//so it will look like active
trans.m_data = new TransactionData(this);
d->transactions.append(trans);
return trans;
}
if (m_driver->d->features & Driver::SingleTransactions) {
if (d->default_trans.active()) {
setError(ERR_TRANSACTION_ACTIVE, i18n("Transaction already started.") );
return Transaction::null;
}
if (!(trans.m_data = drv_beginTransaction())) {
SET_BEGIN_TR_ERROR;
return Transaction::null;
}
d->default_trans = trans;
d->transactions.append(trans);
return d->default_trans;
}
if (m_driver->d->features & Driver::MultipleTransactions) {
if (!(trans.m_data = drv_beginTransaction())) {
SET_BEGIN_TR_ERROR;
return Transaction::null;
}
d->transactions.append(trans);
return trans;
}
SET_ERR_TRANS_NOT_SUPP;
return Transaction::null;
}
bool Connection::commitTransaction(const Transaction trans, bool ignore_inactive)
{
if (!isDatabaseUsed())
return false;
// if (!checkIsDatabaseUsed())
//return false;
if ( !m_driver->transactionsSupported()
&& !(m_driver->d->features & Driver::IgnoreTransactions))
{
SET_ERR_TRANS_NOT_SUPP;
return false;
}
Transaction t = trans;
if (!t.active()) { //try default tr.
if (!d->default_trans.active()) {
if (ignore_inactive)
return true;
clearError();
setError(ERR_NO_TRANSACTION_ACTIVE, i18n("Transaction not started.") );
return false;
}
t = d->default_trans;
d->default_trans = Transaction::null; //now: no default tr.
}
bool ret = true;
if (! (m_driver->d->features & Driver::IgnoreTransactions) )
ret = drv_commitTransaction(t.m_data);
if (t.m_data)
t.m_data->m_active = false; //now this transaction if inactive
if (!d->dont_remove_transactions) //true=transaction obj will be later removed from list
d->transactions.remove(t);
if (!ret && !error())
setError(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, i18n("Error on commit transaction"));
return ret;
}
bool Connection::rollbackTransaction(const Transaction trans, bool ignore_inactive)
{
if (!isDatabaseUsed())
return false;
// if (!checkIsDatabaseUsed())
// return false;
if ( !m_driver->transactionsSupported()
&& !(m_driver->d->features & Driver::IgnoreTransactions))
{
SET_ERR_TRANS_NOT_SUPP;
return false;
}
Transaction t = trans;
if (!t.active()) { //try default tr.
if (!d->default_trans.active()) {
if (ignore_inactive)
return true;
clearError();
setError(ERR_NO_TRANSACTION_ACTIVE, i18n("Transaction not started.") );
return false;
}
t = d->default_trans;
d->default_trans = Transaction::null; //now: no default tr.
}
bool ret = true;
if (! (m_driver->d->features & Driver::IgnoreTransactions) )
ret = drv_rollbackTransaction(t.m_data);
if (t.m_data)
t.m_data->m_active = false; //now this transaction if inactive
if (!d->dont_remove_transactions) //true=transaction obj will be later removed from list
d->transactions.remove(t);
if (!ret && !error())
setError(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, i18n("Error on rollback transaction"));
return ret;
}
#undef SET_ERR_TRANS_NOT_SUPP
#undef SET_BEGIN_TR_ERROR
/*bool Connection::duringTransaction()
{
return drv_duringTransaction();
}*/
Transaction& Connection::defaultTransaction() const
{
return d->default_trans;
}
void Connection::setDefaultTransaction(const Transaction& trans)
{
if (!isDatabaseUsed())
return;
// if (!checkIsDatabaseUsed())
// return;
if ( !(m_driver->d->features & Driver::IgnoreTransactions)
&& (!trans.active() || !m_driver->transactionsSupported()) )
{
return;
}
d->default_trans = trans;
}
const QValueList<Transaction>& Connection::transactions()
{
return d->transactions;
}
bool Connection::autoCommit() const
{
return d->autoCommit;
}
bool Connection::setAutoCommit(bool on)
{
if (d->autoCommit == on || m_driver->d->features & Driver::IgnoreTransactions)
return true;
if (!drv_setAutoCommit(on))
return false;
d->autoCommit = on;
return true;
}
TransactionData* Connection::drv_beginTransaction()
{
QString old_sql = m_sql; //don't
if (!executeSQL( "BEGIN" ))
return 0;
return new TransactionData(this);
}
bool Connection::drv_commitTransaction(TransactionData *)
{
return executeSQL( "COMMIT" );
}
bool Connection::drv_rollbackTransaction(TransactionData *)
{
return executeSQL( "ROLLBACK" );
}
bool Connection::drv_setAutoCommit(bool /*on*/)
{
return true;
}
Cursor* Connection::executeQuery( const QString& statement, uint cursor_options )
{
if (statement.isEmpty())
return 0;
Cursor *c = prepareQuery( statement, cursor_options );
if (!c)
return 0;
if (!c->open()) {//err - kill that
setError(c);
delete c;
return 0;
}
return c;
}
Cursor* Connection::executeQuery( QuerySchema& query, const QValueList<QVariant>& params,
uint cursor_options )
{
Cursor *c = prepareQuery( query, params, cursor_options );
if (!c)
return 0;
if (!c->open()) {//err - kill that
setError(c);
delete c;
return 0;
}
return c;
}
Cursor* Connection::executeQuery( QuerySchema& query, uint cursor_options )
{
return executeQuery(query, QValueList<QVariant>(), cursor_options);
}
Cursor* Connection::executeQuery( TableSchema& table, uint cursor_options )
{
return executeQuery( *table.query(), cursor_options );
}
Cursor* Connection::prepareQuery( TableSchema& table, uint cursor_options )
{
return prepareQuery( *table.query(), cursor_options );
}
Cursor* Connection::prepareQuery( QuerySchema& query, const QValueList<QVariant>& params,
uint cursor_options )
{
Cursor* cursor = prepareQuery(query, cursor_options);
if (cursor)
cursor->setQueryParameters(params);
return cursor;
}
bool Connection::deleteCursor(Cursor *cursor)
{
if (!cursor)
return false;
if (cursor->connection()!=this) {//illegal call
KexiDBWarn << "Connection::deleteCursor(): Cannot delete the cursor not owned by the same connection!" << endl;
return false;
}
const bool ret = cursor->close();
delete cursor;
return ret;
}
bool Connection::setupObjectSchemaData( const RowData &data, SchemaData &sdata )
{
//not found: retrieve schema
/* KexiDB::Cursor *cursor;
if (!(cursor = executeQuery( QString("select * from kexi__objects where o_id='%1'").arg(objId) )))
return false;
if (!cursor->moveFirst()) {
deleteCursor(cursor);
return false;
}*/
//if (!ok) {
//deleteCursor(cursor);
//return 0;
// }
bool ok;
sdata.m_id = data[0].toInt(&ok);
if (!ok) {
return false;
}
sdata.m_name = data[2].toString();
if (!KexiUtils::isIdentifier( sdata.m_name )) {
setError(ERR_INVALID_IDENTIFIER, i18n("Invalid object name \"%1\"").arg(sdata.m_name));
return false;
}
sdata.m_caption = data[3].toString();
sdata.m_desc = data[4].toString();
// KexiDBDbg<<"@@@ Connection::setupObjectSchemaData() == " << sdata.schemaDataDebugString() << endl;
return true;
}
tristate Connection::loadObjectSchemaData( int objectID, SchemaData &sdata )
{
RowData data;
if (true!=querySingleRecord(QString::fromLatin1(
"SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1")
.arg(objectID), data))
return cancelled;
return setupObjectSchemaData( data, sdata );
}
tristate Connection::loadObjectSchemaData( int objectType, const QString& objectName, SchemaData &sdata )
{
RowData data;
if (true!=querySingleRecord(QString::fromLatin1("SELECT o_id, o_type, o_name, o_caption, o_desc "
"FROM kexi__objects WHERE o_type=%1 AND lower(o_name)=%2")
.arg(objectType).arg(m_driver->valueToSQL(Field::Text, objectName.lower())), data))
return cancelled;
return setupObjectSchemaData( data, sdata );
}
bool Connection::storeObjectSchemaData( SchemaData &sdata, bool newObject )
{
TableSchema *ts = d->tables_byname["kexi__objects"];
if (!ts)
return false;
if (newObject) {
int existingID;
if (true == querySingleNumber(QString::fromLatin1(
"SELECT o_id FROM kexi__objects WHERE o_type=%1 AND lower(o_name)=%2")
.arg(sdata.type()).arg(m_driver->valueToSQL(Field::Text, sdata.name().lower())), existingID))
{
//we already have stored a schema data with the same name and type:
//just update it's properties as it would be existing object
sdata.m_id = existingID;
newObject = false;
}
}
if (newObject) {
FieldList *fl;
bool ok;
if (sdata.id()<=0) {//get new ID
fl = ts->subList("o_type", "o_name", "o_caption", "o_desc");
ok = fl!=0;
if (ok && !insertRecord(*fl, QVariant(sdata.type()), QVariant(sdata.name()),
QVariant(sdata.caption()), QVariant(sdata.description()) ))
ok = false;
delete fl;
if (!ok)
return false;
//fetch newly assigned ID
//! @todo safe to cast it?
int obj_id = (int)lastInsertedAutoIncValue("o_id",*ts);
KexiDBDbg << "######## NEW obj_id == " << obj_id << endl;
if (obj_id<=0)
return false;
sdata.m_id = obj_id;
return true;
} else {
fl = ts->subList("o_id", "o_type", "o_name", "o_caption", "o_desc");
ok = fl!=0;
if (ok && !insertRecord(*fl, QVariant(sdata.id()), QVariant(sdata.type()), QVariant(sdata.name()),
QVariant(sdata.caption()), QVariant(sdata.description()) ))
ok = false;
delete fl;
return ok;
}
}
//existing object:
return executeSQL(QString("UPDATE kexi__objects SET o_type=%2, o_caption=%3, o_desc=%4 WHERE o_id=%1")
.arg(sdata.id()).arg(sdata.type())
.arg(m_driver->valueToSQL(KexiDB::Field::Text, sdata.caption()))
.arg(m_driver->valueToSQL(KexiDB::Field::Text, sdata.description())) );
}
tristate Connection::querySingleRecordInternal(RowData &data, const QString* sql, QuerySchema* query,
bool addLimitTo1)
{
Q_ASSERT(sql || query);
//! @todo does not work with non-SQL data sources
if (sql)
m_sql = addLimitTo1 ? (*sql + " LIMIT 1") : *sql; // is this safe?
KexiDB::Cursor *cursor;
if (!(cursor = sql ? executeQuery( m_sql ) : executeQuery( *query ))) {
KexiDBWarn << "Connection::querySingleRecord(): !executeQuery() " << m_sql << endl;
return false;
}
if (!cursor->moveFirst() || cursor->eof()) {
const tristate result = cursor->error() ? false : cancelled;
KexiDBWarn << "Connection::querySingleRecord(): !cursor->moveFirst() || cursor->eof() m_sql=" << m_sql << endl;
setError(cursor);
deleteCursor(cursor);
return result;
}
cursor->storeCurrentRow(data);
return deleteCursor(cursor);
}
tristate Connection::querySingleRecord(const QString& sql, RowData &data, bool addLimitTo1)
{
return querySingleRecordInternal(data, &sql, 0, addLimitTo1);
}
tristate Connection::querySingleRecord(QuerySchema& query, RowData &data, bool addLimitTo1)
{
return querySingleRecordInternal(data, 0, &query, addLimitTo1);
}
bool Connection::checkIfColumnExists(Cursor *cursor, uint column)
{
if (column >= cursor->fieldCount()) {
setError(ERR_CURSOR_RECORD_FETCHING, i18n("Column %1 does not exist for the query.").arg(column));
return false;
}
return true;
}
tristate Connection::querySingleString(const QString& sql, QString &value, uint column, bool addLimitTo1)
{
KexiDB::Cursor *cursor;
m_sql = addLimitTo1 ? (sql + " LIMIT 1") : sql; // is this safe?;
if (!(cursor = executeQuery( m_sql ))) {
KexiDBWarn << "Connection::querySingleRecord(): !executeQuery() " << m_sql << endl;
return false;
}
if (!cursor->moveFirst() || cursor->eof()) {
const tristate result = cursor->error() ? false : cancelled;
KexiDBWarn << "Connection::querySingleRecord(): !cursor->moveFirst() || cursor->eof() " << m_sql << endl;
deleteCursor(cursor);
return result;
}
if (!checkIfColumnExists(cursor, column)) {
deleteCursor(cursor);
return false;
}
value = cursor->value(column).toString();
return deleteCursor(cursor);
}
tristate Connection::querySingleNumber(const QString& sql, int &number, uint column, bool addLimitTo1)
{
static QString str;
static bool ok;
const tristate result = querySingleString(sql, str, column, addLimitTo1);
if (result!=true)
return result;
number = str.toInt(&ok);
return ok;
}
bool Connection::queryStringList(const QString& sql, QStringList& list, uint column)
{
KexiDB::Cursor *cursor;
clearError();
m_sql = sql;
if (!(cursor = executeQuery( m_sql ))) {
KexiDBWarn << "Connection::queryStringList(): !executeQuery() " << m_sql << endl;
return false;
}
cursor->moveFirst();
if (cursor->error()) {
setError(cursor);
deleteCursor(cursor);
return false;
}
if (!cursor->eof() && !checkIfColumnExists(cursor, column)) {
deleteCursor(cursor);
return false;
}
list.clear();
while (!cursor->eof()) {
list.append( cursor->value(column).toString() );
if (!cursor->moveNext() && cursor->error()) {
setError(cursor);
deleteCursor(cursor);
return false;
}
}
return deleteCursor(cursor);
}
bool Connection::resultExists(const QString& sql, bool &success, bool addLimitTo1)
{
KexiDB::Cursor *cursor;
//optimization
if (m_driver->beh->SELECT_1_SUBQUERY_SUPPORTED) {
//this is at least for sqlite
if (addLimitTo1 && sql.left(6).upper() == "SELECT")
m_sql = QString("SELECT 1 FROM (") + sql + ") LIMIT 1"; // is this safe?;
else
m_sql = sql;
}
else {
if (addLimitTo1 && sql.left(6).upper() == "SELECT")
m_sql = sql + " LIMIT 1"; //not always safe!
else
m_sql = sql;
}
if (!(cursor = executeQuery( m_sql ))) {
KexiDBWarn << "Connection::querySingleRecord(): !executeQuery() " << m_sql << endl;
success = false;
return false;
}
if (!cursor->moveFirst() || cursor->eof()) {
success = !cursor->error();
KexiDBWarn << "Connection::querySingleRecord(): !cursor->moveFirst() || cursor->eof() " << m_sql << endl;
setError(cursor);
deleteCursor(cursor);
return false;
}
success = deleteCursor(cursor);
return true;
}
bool Connection::isEmpty( TableSchema& table, bool &success )
{
return !resultExists( selectStatement( *table.query() ), success );
}
//! Used by addFieldPropertyToExtendedTableSchemaData()
static void createExtendedTableSchemaMainElementIfNeeded(
QDomDocument& doc, QDomElement& extendedTableSchemaMainEl,
bool& extendedTableSchemaStringIsEmpty)
{
if (!extendedTableSchemaStringIsEmpty)
return;
//init document
extendedTableSchemaMainEl = doc.createElement("EXTENDED_TABLE_SCHEMA");
doc.appendChild( extendedTableSchemaMainEl );
extendedTableSchemaMainEl.setAttribute("version", QString::number(KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION));
extendedTableSchemaStringIsEmpty = false;
}
//! Used by addFieldPropertyToExtendedTableSchemaData()
static void createExtendedTableSchemaFieldElementIfNeeded(QDomDocument& doc,
QDomElement& extendedTableSchemaMainEl, const QString& fieldName, QDomElement& extendedTableSchemaFieldEl,
bool append = true)
{
if (!extendedTableSchemaFieldEl.isNull())
return;
extendedTableSchemaFieldEl = doc.createElement("field");
if (append)
extendedTableSchemaMainEl.appendChild( extendedTableSchemaFieldEl );
extendedTableSchemaFieldEl.setAttribute("name", fieldName);
}
/*! @internal used by storeExtendedTableSchemaData()
Creates DOM node for \a propertyName and \a propertyValue.
Creates enclosing EXTENDED_TABLE_SCHEMA element if EXTENDED_TABLE_SCHEMA is true.
Updates extendedTableSchemaStringIsEmpty and extendedTableSchemaMainEl afterwards.
If extendedTableSchemaFieldEl is null, creates <field> element (with optional
"custom" attribute is \a custom is false). */
static void addFieldPropertyToExtendedTableSchemaData(
Field *f, const char* propertyName, const QVariant& propertyValue,
QDomDocument& doc, QDomElement& extendedTableSchemaMainEl,
QDomElement& extendedTableSchemaFieldEl,
bool& extendedTableSchemaStringIsEmpty,
bool custom = false )
{
createExtendedTableSchemaMainElementIfNeeded(doc,
extendedTableSchemaMainEl, extendedTableSchemaStringIsEmpty);
createExtendedTableSchemaFieldElementIfNeeded(
doc, extendedTableSchemaMainEl, f->name(), extendedTableSchemaFieldEl);
//create <property>
QDomElement extendedTableSchemaFieldPropertyEl = doc.createElement("property");
extendedTableSchemaFieldEl.appendChild( extendedTableSchemaFieldPropertyEl );
if (custom)
extendedTableSchemaFieldPropertyEl.setAttribute("custom", "true");
extendedTableSchemaFieldPropertyEl.setAttribute("name", propertyName);
QDomElement extendedTableSchemaFieldPropertyValueEl;
switch (propertyValue.type()) {
case QVariant::String:
extendedTableSchemaFieldPropertyValueEl = doc.createElement("string");
break;
case QVariant::CString:
extendedTableSchemaFieldPropertyValueEl = doc.createElement("cstring");
break;
case QVariant::Int:
case QVariant::Double:
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong:
extendedTableSchemaFieldPropertyValueEl = doc.createElement("number");
break;
case QVariant::Bool:
extendedTableSchemaFieldPropertyValueEl = doc.createElement("bool");
break;
default:
//! @todo add more QVariant types
KexiDBFatal << "addFieldPropertyToExtendedTableSchemaData(): impl. error" << endl;
}
extendedTableSchemaFieldPropertyEl.appendChild( extendedTableSchemaFieldPropertyValueEl );
extendedTableSchemaFieldPropertyValueEl.appendChild(
doc.createTextNode( propertyValue.toString() ) );
}
bool Connection::storeExtendedTableSchemaData(TableSchema& tableSchema)
{
//! @todo future: save in older versions if neeed
QDomDocument doc("EXTENDED_TABLE_SCHEMA");
QDomElement extendedTableSchemaMainEl;
bool extendedTableSchemaStringIsEmpty = true;
//for each field:
Field *f;
for (Field::ListIterator it( *tableSchema.fields() ); (f = it.current()); ++it) {
QDomElement extendedTableSchemaFieldEl;
if (f->visibleDecimalPlaces()>=0/*nondefault*/ && KexiDB::supportsVisibleDecimalPlacesProperty(f->type())) {
addFieldPropertyToExtendedTableSchemaData(
f, "visibleDecimalPlaces", f->visibleDecimalPlaces(), doc,
extendedTableSchemaMainEl, extendedTableSchemaFieldEl,
extendedTableSchemaStringIsEmpty );
}
// boolean field with "not null"
// add custom properties
const Field::CustomPropertiesMap customProperties(f->customProperties());
foreach( Field::CustomPropertiesMap::ConstIterator, itCustom, customProperties ) {
addFieldPropertyToExtendedTableSchemaData(
f, itCustom.key(), itCustom.data(), doc,
extendedTableSchemaMainEl, extendedTableSchemaFieldEl, extendedTableSchemaStringIsEmpty,
/*custom*/true );
}
// save lookup table specification, if present
LookupFieldSchema *lookupFieldSchema = tableSchema.lookupFieldSchema( *f );
if (lookupFieldSchema) {
createExtendedTableSchemaFieldElementIfNeeded(
doc, extendedTableSchemaMainEl, f->name(), extendedTableSchemaFieldEl, false/* !append */);
LookupFieldSchema::saveToDom(*lookupFieldSchema, doc, extendedTableSchemaFieldEl);
if (extendedTableSchemaFieldEl.hasChildNodes()) {
// this element provides the definition, so let's append it now
createExtendedTableSchemaMainElementIfNeeded(doc, extendedTableSchemaMainEl,
extendedTableSchemaStringIsEmpty);
extendedTableSchemaMainEl.appendChild( extendedTableSchemaFieldEl );
}
}
}
// Store extended schema information (see ExtendedTableSchemaInformation in Kexi Wiki)
if (extendedTableSchemaStringIsEmpty) {
#ifdef KEXI_DEBUG_GUI
KexiUtils::addAlterTableActionDebug(QString("** Extended table schema REMOVED."));
#endif
if (!removeDataBlock( tableSchema.id(), "extended_schema"))
return false;
}
else {
#ifdef KEXI_DEBUG_GUI
KexiUtils::addAlterTableActionDebug(QString("** Extended table schema set to:\n")+doc.toString(4));
#endif
if (!storeDataBlock( tableSchema.id(), doc.toString(1), "extended_schema" ))
return false;
}
return true;
}
bool Connection::loadExtendedTableSchemaData(TableSchema& tableSchema)
{
#define loadExtendedTableSchemaData_ERR \
{ setError(i18n("Error while loading extended table schema information.")); \
return false; }
#define loadExtendedTableSchemaData_ERR2(details) \
{ setError(i18n("Error while loading extended table schema information."), details); \
return false; }
#define loadExtendedTableSchemaData_ERR3(data) \
{ setError(i18n("Error while loading extended table schema information."), \
i18n("Invalid XML data: ") + data.left(1024) ); \
return false; }
// Load extended schema information, if present (see ExtendedTableSchemaInformation in Kexi Wiki)
QString extendedTableSchemaString;
tristate res = loadDataBlock( tableSchema.id(), extendedTableSchemaString, "extended_schema" );
if (!res)
loadExtendedTableSchemaData_ERR;
// extendedTableSchemaString will be just empty if there is no such data block
#ifdef KEXIDB_LOOKUP_FIELD_TEST
//<temp. for LookupFieldSchema tests>
if (tableSchema.name()=="cars") {
LookupFieldSchema *lookupFieldSchema = new LookupFieldSchema();
lookupFieldSchema->rowSource().setType(LookupFieldSchema::RowSource::Table);
lookupFieldSchema->rowSource().setName("persons");
lookupFieldSchema->setBoundColumn(0); //id
lookupFieldSchema->setVisibleColumn(3); //surname
tableSchema.setLookupFieldSchema( "owner", lookupFieldSchema );
}
//</temp. for LookupFieldSchema tests>
#endif
if (extendedTableSchemaString.isEmpty())
return true;
QDomDocument doc;
QString errorMsg;
int errorLine, errorColumn;
if (!doc.setContent( extendedTableSchemaString, &errorMsg, &errorLine, &errorColumn ))
loadExtendedTableSchemaData_ERR2( i18n("Error in XML data: \"%1\" in line %2, column %3.\nXML data: ")
.arg(errorMsg).arg(errorLine).arg(errorColumn) + extendedTableSchemaString.left(1024));
//! @todo look at the current format version (KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION)
if (doc.doctype().name()!="EXTENDED_TABLE_SCHEMA")
loadExtendedTableSchemaData_ERR3( extendedTableSchemaString );
QDomElement docEl = doc.documentElement();
if (docEl.tagName()!="EXTENDED_TABLE_SCHEMA")
loadExtendedTableSchemaData_ERR3( extendedTableSchemaString );
for (QDomNode n = docEl.firstChild(); !n.isNull(); n = n.nextSibling()) {
QDomElement fieldEl = n.toElement();
if (fieldEl.tagName()=="field") {
Field *f = tableSchema.field( fieldEl.attribute("name") );
if (f) {
//set properties of the field:
//! @todo more properties
for (QDomNode propNode = fieldEl.firstChild();
!propNode.isNull(); propNode = propNode.nextSibling())
{
QDomElement propEl = propNode.toElement();
bool ok;
int intValue;
if (propEl.tagName()=="property") {
QCString propertyName = propEl.attribute("name").latin1();
if (propEl.attribute("custom")=="true") {
//custom property
f->setCustomProperty(propertyName,
KexiDB::loadPropertyValueFromDom( propEl.firstChild() ));
}
else if (propertyName == "visibleDecimalPlaces"
&& KexiDB::supportsVisibleDecimalPlacesProperty(f->type()))
{
intValue = KexiDB::loadIntPropertyValueFromDom( propEl.firstChild(), &ok );
if (ok)
f->setVisibleDecimalPlaces(intValue);
}
//! @todo more properties...
}
else if (propEl.tagName()=="lookup-column") {
LookupFieldSchema *lookupFieldSchema = LookupFieldSchema::loadFromDom(propEl);
if (lookupFieldSchema)
lookupFieldSchema->debug();
tableSchema.setLookupFieldSchema( f->name(), lookupFieldSchema );
}
}
}
else {
KexiDBWarn << "Connection::loadExtendedTableSchemaData(): no such field \""
<< fieldEl.attribute("name") << "\" in table \"" << tableSchema.name() << "\"" << endl;
}
}
}
return true;
}
KexiDB::Field* Connection::setupField( const RowData &data )
{
bool ok = true;
int f_int_type = data.at(1).toInt(&ok);
if (f_int_type<=Field::InvalidType || f_int_type>Field::LastType)
ok = false;
if (!ok)
return 0;
Field::Type f_type = (Field::Type)f_int_type;
int f_len = QMAX( 0, data.at(3).toInt(&ok) );
if (!ok)
return 0;
int f_prec = data.at(4).toInt(&ok);
if (!ok)
return 0;
int f_constr = data.at(5).toInt(&ok);
if (!ok)
return 0;
int f_opts = data.at(6).toInt(&ok);
if (!ok)
return 0;
if (!KexiUtils::isIdentifier( data.at(2).toString() )) {
setError(ERR_INVALID_IDENTIFIER, i18n("Invalid object name \"%1\"")
.arg( data.at(2).toString() ));
ok = false;
return 0;
}
Field *f = new Field(
data.at(2).toString(), f_type, f_constr, f_opts, f_len, f_prec );
f->setDefaultValue( KexiDB::stringToVariant(data.at(7).toString(), Field::variantType( f_type ), ok) );
if (!ok) {
KexiDBWarn << "Connection::setupTableSchema() problem with KexiDB::stringToVariant("
<< data.at(7).toString() << ")" << endl;
}
ok = true; //problem with defaultValue is not critical
f->m_caption = data.at(9).toString();
f->m_desc = data.at(10).toString();
return f;
}
KexiDB::TableSchema* Connection::setupTableSchema( const RowData &data )
{
TableSchema *t = new TableSchema( this );
if (!setupObjectSchemaData( data, *t )) {
delete t;
return 0;
}
KexiDB::Cursor *cursor;
if (!(cursor = executeQuery(
QString::fromLatin1("SELECT t_id, f_type, f_name, f_length, f_precision, f_constraints, "
"f_options, f_default, f_order, f_caption, f_help"
" FROM kexi__fields WHERE t_id=%1 ORDER BY f_order").arg(t->m_id) )))
{
delete t;
return 0;
}
if (!cursor->moveFirst()) {
if (!cursor->error() && cursor->eof()) {
setError(i18n("Table has no fields defined."));
}
deleteCursor(cursor);
delete t;
return 0;
}
// For each field: load its schema
RowData fieldData;
bool ok = true;
while (!cursor->eof()) {
// KexiDBDbg<<"@@@ f_name=="<<cursor->value(2).asCString()<<endl;
cursor->storeCurrentRow(fieldData);
Field *f = setupField(fieldData);
if (!f) {
ok = false;
break;
}
t->addField(f);
cursor->moveNext();
}
if (!ok) {//error:
deleteCursor(cursor);
delete t;
return 0;
}
if (!deleteCursor(cursor)) {
delete t;
return 0;
}
if (!loadExtendedTableSchemaData(*t)) {
delete t;
return 0;
}
//store locally:
d->tables.insert(t->m_id, t);
d->tables_byname.insert(t->m_name.lower(), t);
return t;
}
TableSchema* Connection::tableSchema( const QString& tableName )
{
QString m_tableName = tableName.lower();
TableSchema *t = d->tables_byname[m_tableName];
if (t)
return t;
//not found: retrieve schema
RowData data;
if (true!=querySingleRecord(QString::fromLatin1(
"SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE lower(o_name)='%1' AND o_type=%2")
.arg(m_tableName).arg(KexiDB::TableObjectType), data))
return 0;
return setupTableSchema(data);
}
TableSchema* Connection::tableSchema( int tableId )
{
TableSchema *t = d->tables[tableId];
if (t)
return t;
//not found: retrieve schema
RowData data;
if (true!=querySingleRecord(QString::fromLatin1(
"SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1")
.arg(tableId), data))
return 0;
return setupTableSchema(data);
}
tristate Connection::loadDataBlock( int objectID, QString &dataString, const QString& dataID )
{
if (objectID<=0)
return false;
return querySingleString(
QString("SELECT o_data FROM kexi__objectdata WHERE o_id=") + QString::number(objectID)
+ " AND " + KexiDB::sqlWhere(m_driver, KexiDB::Field::Text, "o_sub_id", dataID),
dataString );
}
bool Connection::storeDataBlock( int objectID, const QString &dataString, const QString& dataID )
{
if (objectID<=0)
return false;
QString sql(QString::fromLatin1("SELECT kexi__objectdata.o_id FROM kexi__objectdata WHERE o_id=%1").arg(objectID));
QString sql_sub( KexiDB::sqlWhere(m_driver, KexiDB::Field::Text, "o_sub_id", dataID) );
bool ok, exists;
exists = resultExists(sql + " and " + sql_sub, ok);
if (!ok)
return false;
if (exists) {
return executeSQL( "UPDATE kexi__objectdata SET o_data="
+ m_driver->valueToSQL( KexiDB::Field::LongText, dataString )
+ " WHERE o_id=" + QString::number(objectID) + " AND " + sql_sub );
}
return executeSQL(
QString::fromLatin1("INSERT INTO kexi__objectdata (o_id, o_data, o_sub_id) VALUES (")
+ QString::number(objectID) +"," + m_driver->valueToSQL( KexiDB::Field::LongText, dataString )
+ "," + m_driver->valueToSQL( KexiDB::Field::Text, dataID ) + ")" );
}
bool Connection::removeDataBlock( int objectID, const QString& dataID)
{
if (objectID<=0)
return false;
if (dataID.isEmpty())
return KexiDB::deleteRow(*this, "kexi__objectdata", "o_id", QString::number(objectID));
else
return KexiDB::deleteRow(*this, "kexi__objectdata",
"o_id", KexiDB::Field::Integer, objectID, "o_sub_id", KexiDB::Field::Text, dataID);
}
KexiDB::QuerySchema* Connection::setupQuerySchema( const RowData &data )
{
bool ok = true;
const int objID = data[0].toInt(&ok);
if (!ok)
return false;
QString sqlText;
if (!loadDataBlock( objID, sqlText, "sql" )) {
setError(ERR_OBJECT_NOT_FOUND,
i18n("Could not find definition for query \"%1\". Removing this query is recommended.")
.arg(data[2].toString()));
return 0;
}
d->parser()->parse( sqlText );
KexiDB::QuerySchema *query = d->parser()->query();
//error?
if (!query) {
setError(ERR_SQL_PARSE_ERROR,
i18n("<p>Could not load definition for query \"%1\". "
"SQL statement for this query is invalid:<br><tt>%2</tt></p>\n"
"<p>You can open this query in Text View and correct it.</p>").arg(data[2].toString())
.arg(d->parser()->statement()));
return 0;
}
if (!setupObjectSchemaData( data, *query )) {
delete query;
return 0;
}
d->queries.insert(query->m_id, query);
d->queries_byname.insert(query->m_name, query);
return query;
}
QuerySchema* Connection::querySchema( const QString& queryName )
{
QString m_queryName = queryName.lower();
QuerySchema *q = d->queries_byname[m_queryName];
if (q)
return q;
//not found: retrieve schema
RowData data;
if (true!=querySingleRecord(QString::fromLatin1(
"SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE lower(o_name)='%1' AND o_type=%2")
.arg(m_queryName).arg(KexiDB::QueryObjectType), data))
return 0;
return setupQuerySchema(data);
}
QuerySchema* Connection::querySchema( int queryId )
{
QuerySchema *q = d->queries[queryId];
if (q)
return q;
//not found: retrieve schema
clearError();
RowData data;
if (true!=querySingleRecord(QString::fromLatin1(
"SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1").arg(queryId), data))
return 0;
return setupQuerySchema(data);
}
bool Connection::setQuerySchemaObsolete( const QString& queryName )
{
QuerySchema* oldQuery = querySchema( queryName );
if (!oldQuery)
return false;
d->obsoleteQueries.append(oldQuery);
d->queries_byname.take(queryName);
d->queries.take(oldQuery->id());
return true;
}
TableSchema* Connection::newKexiDBSystemTableSchema(const QString& tsname)
{
TableSchema *ts = new TableSchema(tsname.lower());
insertInternalTableSchema( ts );
return ts;
}
bool Connection::isInternalTableSchema(const QString& tableName)
{
return (d->kexiDBSystemTables[ d->tables_byname[tableName] ])
// these are here for compatiblility because we're no longer instantiate
// them but can exist in projects created with previous Kexi versions:
|| tableName=="kexi__final" || tableName=="kexi__useractions";
}
void Connection::insertInternalTableSchema(TableSchema *tableSchema)
{
tableSchema->setKexiDBSystem(true);
d->kexiDBSystemTables.insert(tableSchema, tableSchema);
d->tables_byname.insert(tableSchema->name(), tableSchema);
}
//! Creates kexi__* tables.
bool Connection::setupKexiDBSystemSchema()
{
if (!d->kexiDBSystemTables.isEmpty())
return true; //already set up
TableSchema *t_objects = newKexiDBSystemTableSchema("kexi__objects");
t_objects->addField( new Field("o_id", Field::Integer, Field::PrimaryKey | Field::AutoInc, Field::Unsigned) )
.addField( new Field("o_type", Field::Byte, 0, Field::Unsigned) )
.addField( new Field("o_name", Field::Text) )
.addField( new Field("o_caption", Field::Text ) )
.addField( new Field("o_desc", Field::LongText ) );
t_objects->debug();
TableSchema *t_objectdata = newKexiDBSystemTableSchema("kexi__objectdata");
t_objectdata->addField( new Field("o_id", Field::Integer, Field::NotNull, Field::Unsigned) )
.addField( new Field("o_data", Field::LongText) )
.addField( new Field("o_sub_id", Field::Text) );
TableSchema *t_fields = newKexiDBSystemTableSchema("kexi__fields");
t_fields->addField( new Field("t_id", Field::Integer, 0, Field::Unsigned) )
.addField( new Field("f_type", Field::Byte, 0, Field::Unsigned) )
.addField( new Field("f_name", Field::Text ) )
.addField( new Field("f_length", Field::Integer ) )
.addField( new Field("f_precision", Field::Integer ) )
.addField( new Field("f_constraints", Field::Integer ) )
.addField( new Field("f_options", Field::Integer ) )
.addField( new Field("f_default", Field::Text ) )
//these are additional properties:
.addField( new Field("f_order", Field::Integer ) )
.addField( new Field("f_caption", Field::Text ) )
.addField( new Field("f_help", Field::LongText ) );
/* TableSchema *t_querydata = newKexiDBSystemTableSchema("kexi__querydata");
t_querydata->addField( new Field("q_id", Field::Integer, 0, Field::Unsigned) )
.addField( new Field("q_sql", Field::LongText ) )
.addField( new Field("q_valid", Field::Boolean ) );
TableSchema *t_queryfields = newKexiDBSystemTableSchema("kexi__queryfields");
t_queryfields->addField( new Field("q_id", Field::Integer, 0, Field::Unsigned) )
.addField( new Field("f_order", Field::Integer ) )
.addField( new Field("f_id", Field::Integer ) )
.addField( new Field("f_tab_asterisk", Field::Integer, 0, Field::Unsigned) )
.addField( new Field("f_alltab_asterisk", Field::Boolean) );
TableSchema *t_querytables = newKexiDBSystemTableSchema("kexi__querytables");
t_querytables->addField( new Field("q_id", Field::Integer, 0, Field::Unsigned) )
.addField( new Field("t_id", Field::Integer, 0, Field::Unsigned) )
.addField( new Field("t_order", Field::Integer, 0, Field::Unsigned) );*/
TableSchema *t_db = newKexiDBSystemTableSchema("kexi__db");
t_db->addField( new Field("db_property", Field::Text, Field::NoConstraints, Field::NoOptions, 32 ) )
.addField( new Field("db_value", Field::LongText ) );
/* moved to KexiProject...
TableSchema *t_parts = newKexiDBSystemTableSchema("kexi__parts");
t_parts->addField( new Field("p_id", Field::Integer, Field::PrimaryKey | Field::AutoInc, Field::Unsigned) )
.addField( new Field("p_name", Field::Text) )
.addField( new Field("p_mime", Field::Text ) )
.addField( new Field("p_url", Field::Text ) );
*/
/*UNUSED
TableSchema *t_final = newKexiDBSystemTableSchema("kexi__final");
t_final->addField( new Field("p_id", Field::Integer, 0, Field::Unsigned) )
.addField( new Field("property", Field::LongText ) )
.addField( new Field("value", Field::BLOB) );
TableSchema *t_useractions = newKexiDBSystemTableSchema("kexi__useractions");
t_useractions->addField( new Field("p_id", Field::Integer, 0, Field::Unsigned) )
.addField( new Field("scope", Field::Integer ) )
.addField( new Field("name", Field::LongText ) )
.addField( new Field("text", Field::LongText ) )
.addField( new Field("icon", Field::LongText ) )
.addField( new Field("method", Field::Integer ) )
.addField( new Field("arguments", Field::LongText) );
*/
return true;
}
void Connection::removeMe(TableSchema *ts)
{
if (ts && !m_destructor_started) {
d->tables.take(ts->id());
d->tables_byname.take(ts->name());
}
}
QString Connection::anyAvailableDatabaseName()
{
if (!d->availableDatabaseName.isEmpty()) {
return d->availableDatabaseName;
}
return m_driver->beh->ALWAYS_AVAILABLE_DATABASE_NAME;
}
void Connection::setAvailableDatabaseName(const QString& dbName)
{
d->availableDatabaseName = dbName;
}
//! @internal used in updateRow(), insertRow(),
inline void updateRowDataWithNewValues(QuerySchema &query, RowData& data, KexiDB::RowEditBuffer::DBMap& b,
QMap<QueryColumnInfo*,int>& columnsOrderExpanded)
{
columnsOrderExpanded = query.columnsOrder(QuerySchema::ExpandedList);
QMap<QueryColumnInfo*,int>::ConstIterator columnsOrderExpandedIt;
for (KexiDB::RowEditBuffer::DBMap::ConstIterator it=b.constBegin();it!=b.constEnd();++it) {
columnsOrderExpandedIt = columnsOrderExpanded.find( it.key() );
if (columnsOrderExpandedIt == columnsOrderExpanded.constEnd()) {
KexiDBWarn << "(Connection) updateRowDataWithNewValues(): \"now also assign new value in memory\" step "
"- could not find item '" << it.key()->aliasOrName() << "'" << endl;
continue;
}
data[ columnsOrderExpandedIt.data() ] = it.data();
}
}
bool Connection::updateRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool useROWID)
{
// Each SQL identifier needs to be escaped in the generated query.
// query.debug();
KexiDBDbg << "Connection::updateRow.." << endl;
clearError();
//--get PKEY
if (buf.dbBuffer().isEmpty()) {
KexiDBDbg << " -- NO CHANGES DATA!" << endl;
return true;
}
TableSchema *mt = query.masterTable();
if (!mt) {
KexiDBWarn << " -- NO MASTER TABLE!" << endl;
setError(ERR_UPDATE_NO_MASTER_TABLE,
i18n("Could not update row because there is no master table defined."));
return false;
}
IndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0;
if (!useROWID && !pkey) {
KexiDBWarn << " -- NO MASTER TABLE's PKEY!" << endl;
setError(ERR_UPDATE_NO_MASTER_TABLES_PKEY,
i18n("Could not update row because master table has no primary key defined."));
//! @todo perhaps we can try to update without using PKEY?
return false;
}
//update the record:
m_sql = "UPDATE " + escapeIdentifier(mt->name()) + " SET ";
QString sqlset, sqlwhere;
sqlset.reserve(1024);
sqlwhere.reserve(1024);
KexiDB::RowEditBuffer::DBMap b = buf.dbBuffer();
for (KexiDB::RowEditBuffer::DBMap::ConstIterator it=b.constBegin();it!=b.constEnd();++it) {
if (it.key()->field->table()!=mt)
continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field)
if (!sqlset.isEmpty())
sqlset+=",";
sqlset += (escapeIdentifier(it.key()->field->name()) + "=" +
m_driver->valueToSQL(it.key()->field,it.data()));
}
if (pkey) {
const QValueVector<int> pkeyFieldsOrder( query.pkeyFieldsOrder() );
KexiDBDbg << pkey->fieldCount() << " ? " << query.pkeyFieldsCount() << endl;
if (pkey->fieldCount() != query.pkeyFieldsCount()) { //sanity check
KexiDBWarn << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!" << endl;
setError(ERR_UPDATE_NO_ENTIRE_MASTER_TABLES_PKEY,
i18n("Could not update row because it does not contain entire master table's primary key."));
return false;
}
if (!pkey->fields()->isEmpty()) {
uint i=0;
for (Field::ListIterator it = pkey->fieldsIterator(); it.current(); i++, ++it) {
if (!sqlwhere.isEmpty())
sqlwhere+=" AND ";
QVariant val = data[ pkeyFieldsOrder[i] ];
if (val.isNull() || !val.isValid()) {
setError(ERR_UPDATE_NULL_PKEY_FIELD,
i18n("Primary key's field \"%1\" cannot be empty.").arg(it.current()->name()));
//js todo: pass the field's name somewhere!
return false;
}
sqlwhere += ( escapeIdentifier(it.current()->name()) + "=" +
m_driver->valueToSQL( it.current(), val ) );
}
}
}
else {//use ROWID
sqlwhere = ( escapeIdentifier(m_driver->beh->ROW_ID_FIELD_NAME) + "="
+ m_driver->valueToSQL(Field::BigInteger, data[data.size()-1]));
}
m_sql += (sqlset + " WHERE " + sqlwhere);
KexiDBDbg << " -- SQL == " << ((m_sql.length() > 400) ? (m_sql.left(400)+"[.....]") : m_sql) << endl;
if (!executeSQL(m_sql)) {
setError(ERR_UPDATE_SERVER_ERROR, i18n("Row updating on the server failed."));
return false;
}
//success: now also assign new values in memory:
QMap<QueryColumnInfo*,int> columnsOrderExpanded;
updateRowDataWithNewValues(query, data, b, columnsOrderExpanded);
return true;
}
bool Connection::insertRow(QuerySchema &query, RowData& data, RowEditBuffer& buf, bool getROWID)
{
// Each SQL identifier needs to be escaped in the generated query.
KexiDBDbg << "Connection::updateRow.." << endl;
clearError();
//--get PKEY
/*disabled: there may be empty rows (with autoinc)
if (buf.dbBuffer().isEmpty()) {
KexiDBDbg << " -- NO CHANGES DATA!" << endl;
return true; }*/
TableSchema *mt = query.masterTable();
if (!mt) {
KexiDBWarn << " -- NO MASTER TABLE!" << endl;
setError(ERR_INSERT_NO_MASTER_TABLE,
i18n("Could not insert row because there is no master table defined."));
return false;
}
IndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0;
if (!getROWID && !pkey)
KexiDBWarn << " -- WARNING: NO MASTER TABLE's PKEY" << endl;
QString sqlcols, sqlvals;
sqlcols.reserve(1024);
sqlvals.reserve(1024);
//insert the record:
m_sql = "INSERT INTO " + escapeIdentifier(mt->name()) + " (";
KexiDB::RowEditBuffer::DBMap b = buf.dbBuffer();
// add default values, if available (for any column without value explicitly set)
const QueryColumnInfo::Vector fieldsExpanded( query.fieldsExpanded( QuerySchema::Unique ) );
for (uint i=0; i<fieldsExpanded.count(); i++) {
QueryColumnInfo *ci = fieldsExpanded.at(i);
if (ci->field && KexiDB::isDefaultValueAllowed(ci->field)
&& !ci->field->defaultValue().isNull()
&& !b.contains( ci ))
{
KexiDBDbg << "Connection::insertRow(): adding default value '" << ci->field->defaultValue().toString()
<< "' for column '" << ci->field->name() << "'" << endl;
b.insert( ci, ci->field->defaultValue() );
}
}
if (b.isEmpty()) {
// empty row inserting requested:
if (!getROWID && !pkey) {
KexiDBWarn << "MASTER TABLE's PKEY REQUIRED FOR INSERTING EMPTY ROWS: INSERT CANCELLED" << endl;
setError(ERR_INSERT_NO_MASTER_TABLES_PKEY,
i18n("Could not insert row because master table has no primary key defined."));
return false;
}
if (pkey) {
const QValueVector<int> pkeyFieldsOrder( query.pkeyFieldsOrder() );
// KexiDBDbg << pkey->fieldCount() << " ? " << query.pkeyFieldsCount() << endl;
if (pkey->fieldCount() != query.pkeyFieldsCount()) { //sanity check
KexiDBWarn << "NO ENTIRE MASTER TABLE's PKEY SPECIFIED!" << endl;
setError(ERR_INSERT_NO_ENTIRE_MASTER_TABLES_PKEY,
i18n("Could not insert row because it does not contain entire master table's primary key.")
.arg(query.name()));
return false;
}
}
//at least one value is needed for VALUES section: find it and set to NULL:
Field *anyField = mt->anyNonPKField();
if (!anyField) {
if (!pkey) {
KexiDBWarn << "WARNING: NO FIELD AVAILABLE TO SET IT TO NULL" << endl;
return false;
}
else {
//try to set NULL in pkey field (could not work for every SQL engine!)
anyField = pkey->fields()->first();
}
}
sqlcols += escapeIdentifier(anyField->name());
sqlvals += m_driver->valueToSQL(anyField,QVariant()/*NULL*/);
}
else {
// non-empty row inserting requested:
for (KexiDB::RowEditBuffer::DBMap::ConstIterator it=b.constBegin();it!=b.constEnd();++it) {
if (it.key()->field->table()!=mt)
continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field)
if (!sqlcols.isEmpty()) {
sqlcols+=",";
sqlvals+=",";
}
sqlcols += escapeIdentifier(it.key()->field->name());
sqlvals += m_driver->valueToSQL(it.key()->field,it.data());
}
}
m_sql += (sqlcols + ") VALUES (" + sqlvals + ")");
// KexiDBDbg << " -- SQL == " << m_sql << endl;
bool res = executeSQL(m_sql);
if (!res) {
setError(ERR_INSERT_SERVER_ERROR, i18n("Row inserting on the server failed."));
return false;
}
//success: now also assign a new value in memory:
QMap<QueryColumnInfo*,int> columnsOrderExpanded;
updateRowDataWithNewValues(query, data, b, columnsOrderExpanded);
//fetch autoincremented values
QueryColumnInfo::List *aif_list = query.autoIncrementFields();
Q_ULLONG ROWID = 0;
if (pkey && !aif_list->isEmpty()) {
//! @todo now only if PKEY is present, this should also work when there's no PKEY
QueryColumnInfo *id_columnInfo = aif_list->first();
//! @todo safe to cast it?
Q_ULLONG last_id = lastInsertedAutoIncValue(
id_columnInfo->field->name(), id_columnInfo->field->table()->name(), &ROWID);
if (last_id==(Q_ULLONG)-1 || last_id<=0) {
//! @todo show error
//! @todo remove just inserted row. How? Using ROLLBACK?
return false;
}
RowData aif_data;
QString getAutoIncForInsertedValue = QString::fromLatin1("SELECT ")
+ query.autoIncrementSQLFieldsList(m_driver)
+ QString::fromLatin1(" FROM ")
+ escapeIdentifier(id_columnInfo->field->table()->name())
+ QString::fromLatin1(" WHERE ")
+ escapeIdentifier(id_columnInfo->field->name()) + "="
+ QString::number(last_id);
if (true!=querySingleRecord(getAutoIncForInsertedValue, aif_data)) {
//! @todo show error
return false;
}
QueryColumnInfo::ListIterator ci_it(*aif_list);
QueryColumnInfo *ci;
for (uint i=0; (ci = ci_it.current()); ++ci_it, i++) {
// KexiDBDbg << "Connection::insertRow(): AUTOINCREMENTED FIELD " << fi->field->name() << " == "
// << aif_data[i].toInt() << endl;
( data[ columnsOrderExpanded[ ci ] ] = aif_data[i] ).cast( ci->field->variantType() ); //cast to get proper type
}
}
else {
ROWID = drv_lastInsertRowID();
// KexiDBDbg << "Connection::insertRow(): new ROWID == " << (uint)ROWID << endl;
if (m_driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE) {
KexiDBWarn << "Connection::insertRow(): m_driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE" << endl;
return false;
}
}
if (getROWID && /*sanity check*/data.size() > fieldsExpanded.size()) {
// KexiDBDbg << "Connection::insertRow(): new ROWID == " << (uint)ROWID << endl;
data[data.size()-1] = ROWID;
}
return true;
}
bool Connection::deleteRow(QuerySchema &query, RowData& data, bool useROWID)
{
// Each SQL identifier needs to be escaped in the generated query.
KexiDBWarn << "Connection::deleteRow.." << endl;
clearError();
TableSchema *mt = query.masterTable();
if (!mt) {
KexiDBWarn << " -- NO MASTER TABLE!" << endl;
setError(ERR_DELETE_NO_MASTER_TABLE,
i18n("Could not delete row because there is no master table defined.")
.arg(query.name()));
return false;
}
IndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0;
//! @todo allow to delete from a table without pkey
if (!useROWID && !pkey) {
KexiDBWarn << " -- WARNING: NO MASTER TABLE's PKEY" << endl;
setError(ERR_DELETE_NO_MASTER_TABLES_PKEY,
i18n("Could not delete row because there is no primary key for master table defined."));
return false;
}
//update the record:
m_sql = "DELETE FROM " + escapeIdentifier(mt->name()) + " WHERE ";
QString sqlwhere;
sqlwhere.reserve(1024);
if (pkey) {
const QValueVector<int> pkeyFieldsOrder( query.pkeyFieldsOrder() );
KexiDBDbg << pkey->fieldCount() << " ? " << query.pkeyFieldsCount() << endl;
if (pkey->fieldCount() != query.pkeyFieldsCount()) { //sanity check
KexiDBWarn << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!" << endl;
setError(ERR_DELETE_NO_ENTIRE_MASTER_TABLES_PKEY,
i18n("Could not delete row because it does not contain entire master table's primary key."));
return false;
}
uint i=0;
for (Field::ListIterator it = pkey->fieldsIterator(); it.current(); i++, ++it) {
if (!sqlwhere.isEmpty())
sqlwhere+=" AND ";
QVariant val = data[ pkeyFieldsOrder[i] ];
if (val.isNull() || !val.isValid()) {
setError(ERR_DELETE_NULL_PKEY_FIELD, i18n("Primary key's field \"%1\" cannot be empty.")
.arg(it.current()->name()));
//js todo: pass the field's name somewhere!
return false;
}
sqlwhere += ( escapeIdentifier(it.current()->name()) + "=" +
m_driver->valueToSQL( it.current(), val ) );
}
}
else {//use ROWID
sqlwhere = ( escapeIdentifier(m_driver->beh->ROW_ID_FIELD_NAME) + "="
+ m_driver->valueToSQL(Field::BigInteger, data[data.size()-1]));
}
m_sql += sqlwhere;
KexiDBDbg << " -- SQL == " << m_sql << endl;
if (!executeSQL(m_sql)) {
setError(ERR_DELETE_SERVER_ERROR, i18n("Row deletion on the server failed."));
return false;
}
return true;
}
bool Connection::deleteAllRows(QuerySchema &query)
{
clearError();
TableSchema *mt = query.masterTable();
if (!mt) {
KexiDBWarn << " -- NO MASTER TABLE!" << endl;
return false;
}
IndexSchema *pkey = mt->primaryKey();
if (!pkey || pkey->fields()->isEmpty())
KexiDBWarn << "Connection::deleteAllRows -- WARNING: NO MASTER TABLE's PKEY" << endl;
m_sql = "DELETE FROM " + escapeIdentifier(mt->name());
KexiDBDbg << " -- SQL == " << m_sql << endl;
if (!executeSQL(m_sql)) {
setError(ERR_DELETE_SERVER_ERROR, i18n("Row deletion on the server failed."));
return false;
}
return true;
}
void Connection::registerForTableSchemaChanges(TableSchemaChangeListenerInterface& listener,
TableSchema &schema)
{
QPtrList<TableSchemaChangeListenerInterface>* listeners = d->tableSchemaChangeListeners[&schema];
if (!listeners) {
listeners = new QPtrList<TableSchemaChangeListenerInterface>();
d->tableSchemaChangeListeners.insert(&schema, listeners);
}
//TODO: inefficient
if (listeners->findRef( &listener )==-1)
listeners->append( &listener );
}
void Connection::unregisterForTableSchemaChanges(TableSchemaChangeListenerInterface& listener,
TableSchema &schema)
{
QPtrList<TableSchemaChangeListenerInterface>* listeners = d->tableSchemaChangeListeners[&schema];
if (!listeners)
return;
//TODO: inefficient
listeners->remove( &listener );
}
void Connection::unregisterForTablesSchemaChanges(TableSchemaChangeListenerInterface& listener)
{
for (QPtrDictIterator< QPtrList<TableSchemaChangeListenerInterface> > it(d->tableSchemaChangeListeners);
it.current(); ++it)
{
if (-1!=it.current()->find(&listener))
it.current()->take();
}
}
QPtrList<Connection::TableSchemaChangeListenerInterface>*
Connection::tableSchemaChangeListeners(TableSchema& tableSchema) const
{
KexiDBDbg << d->tableSchemaChangeListeners.count() << endl;
return d->tableSchemaChangeListeners[&tableSchema];
}
tristate Connection::closeAllTableSchemaChangeListeners(TableSchema& tableSchema)
{
QPtrList<Connection::TableSchemaChangeListenerInterface> *listeners = d->tableSchemaChangeListeners[&tableSchema];
if (!listeners)
return true;
QPtrListIterator<KexiDB::Connection::TableSchemaChangeListenerInterface> tmpListeners(*listeners); //safer copy
tristate res = true;
//try to close every window
for (QPtrListIterator<KexiDB::Connection::TableSchemaChangeListenerInterface> it(tmpListeners);
it.current() && res==true; ++it)
{
res = it.current()->closeListener();
}
return res;
}
/*PreparedStatement::Ptr Connection::prepareStatement(PreparedStatement::StatementType,
TableSchema&)
{
//safe?
return 0;
}*/
void Connection::setReadOnly(bool set)
{
if (d->isConnected)
return; //sanity
d->readOnly = set;
}
bool Connection::isReadOnly() const
{
return d->readOnly;
}
#include "connection.moc"