summaryrefslogtreecommitdiffstats
path: root/kexi/kexidb/connection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kexi/kexidb/connection.cpp')
-rw-r--r--kexi/kexidb/connection.cpp3552
1 files changed, 3552 insertions, 0 deletions
diff --git a/kexi/kexidb/connection.cpp b/kexi/kexidb/connection.cpp
new file mode 100644
index 00000000..1a401a8a
--- /dev/null
+++ b/kexi/kexidb/connection.cpp
@@ -0,0 +1,3552 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]>
+
+ 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"