/* This file is part of the KDE project Copyright (C) 2003-2007 Jaroslaw Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #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 #include #include #include #include #include #include #include #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 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 transactions; QPtrDict< QPtrList > tableSchemaChangeListeners; //! Used in Connection::setQuerySchemaObsolete( const QString& queryName ) //! to collect obsolete queries. THese are deleted on connection deleting. QPtrList 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 tables; QDict tables_byname; QIntDict queries; QDict queries_byname; //! used just for removing system TableSchema objects on db close. QPtrDict 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("<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 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::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 Connection::tableIds() { return objectIds(KexiDB::TableObjectType); } QValueList Connection::queryIds() { return objectIds(KexiDB::QueryObjectType); } QValueList Connection::objectIds(int objType) { QValueList 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 + ")" <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& 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::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& 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::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& 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 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(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 visibleColumns( lookupFieldSchema->visibleColumns() ); QString expression; foreach (QValueList::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 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 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 : ¶mValuesIt; 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 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::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& 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 vals; buildValuesForKexi__Fields(vals, field); QValueList::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!" <fieldCount()<1) { clearError(); setError(ERR_CANNOT_CREATE_EMPTY_OBJECT, i18n("Cannot create table without fields.")); return false; } const bool internalTable = dynamic_cast(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 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<<"******** "<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& 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& 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(), 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& 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 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 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 // 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 ); } // #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=="<value(2).asCString()<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("

Could not load definition for query \"%1\". " "SQL statement for this query is invalid:
%2

\n" "

You can open this query in Text View and correct it.

").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& columnsOrderExpanded) { columnsOrderExpanded = query.columnsOrder(QuerySchema::ExpandedList); QMap::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 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 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; ifield && 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 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 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 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* listeners = d->tableSchemaChangeListeners[&schema]; if (!listeners) { listeners = new QPtrList(); d->tableSchemaChangeListeners.insert(&schema, listeners); } //TODO: inefficient if (listeners->findRef( &listener )==-1) listeners->append( &listener ); } void Connection::unregisterForTableSchemaChanges(TableSchemaChangeListenerInterface& listener, TableSchema &schema) { QPtrList* listeners = d->tableSchemaChangeListeners[&schema]; if (!listeners) return; //TODO: inefficient listeners->remove( &listener ); } void Connection::unregisterForTablesSchemaChanges(TableSchemaChangeListenerInterface& listener) { for (QPtrDictIterator< QPtrList > it(d->tableSchemaChangeListeners); it.current(); ++it) { if (-1!=it.current()->find(&listener)) it.current()->take(); } } QPtrList* Connection::tableSchemaChangeListeners(TableSchema& tableSchema) const { KexiDBDbg << d->tableSchemaChangeListeners.count() << endl; return d->tableSchemaChangeListeners[&tableSchema]; } tristate Connection::closeAllTableSchemaChangeListeners(TableSchema& tableSchema) { QPtrList *listeners = d->tableSchemaChangeListeners[&tableSchema]; if (!listeners) return true; QPtrListIterator tmpListeners(*listeners); //safer copy tristate res = true; //try to close every window for (QPtrListIterator 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"