/* 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(1009) , m_parser(0) , tables_byname(1009, false) , queries_byname(1009, 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(1009); queries.resize(1009); } ~ConnectionPrivate() { delete m_parser; } void errorInvalidDBContents(const TQString& details) { conn->setError( ERR_INVALID_DATABASE_CONTENTS, i18n("Invalid database contents. ")+details); } TQString 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. TQGuardedPtr 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; TQValueList transactions; TQPtrDict< TQPtrList > tableSchemaChangeListeners; //! Used in Connection::setQuerySchemaObsolete( const TQString& queryName ) //! to collect obsolete queries. THese are deleted on connection deleting. TQPtrList 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() TQIntDict tables; TQDict tables_byname; TQIntDict queries; TQDict queries_byname; //! used just for removing system TableSchema objects on db close. TQPtrDict kexiDBSystemTables; //! Database properties DatabaseProperties* dbProperties; TQString availableDatabaseName; //!< used by anyAvailableDatabaseName() TQString 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 TQStringList KexiDB_kexiDBSystemTableNames; Connection::Connection( Driver *driver, ConnectionData &conn_data ) : TQObject() ,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(TQDir::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 = TQString(); } 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; } TQStringList 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( TQStringList &list ) { list.clear(); return true; } bool Connection::drv_databaseExists( const TQString &dbName, bool ignoreErrors ) { TQStringList 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 TQString &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(): TQFileInfo 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(TQDir::convertSeparators(d->conn_data->fileName())) ); return false; } if (!file.isReadable()) { if (!ignoreErrors) setError(ERR_ACCESS_RIGHTS, i18n("Database file \"%1\" is not readable.") .arg(TQDir::convertSeparators(d->conn_data->fileName())) ); return false; } if (!file.isWritable()) { if (!ignoreErrors) setError(ERR_ACCESS_RIGHTS, i18n("Database file \"%1\" is not writable.") .arg(TQDir::convertSeparators(d->conn_data->fileName())) ); return false; } return true; } TQString 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 TQString &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 ); } TQString 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 (TQPtrDictIterator 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, TQVariant(1), TQVariant("Tables"), TQVariant("kexi/table"), TQVariant("http://koffice.org/kexi/"))) createDatabase_ERROR; if (!insertRecord(*fl, TQVariant(2), TQVariant("Queries"), TQVariant("kexi/query"), TQVariant("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 TQString &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; TQString my_dbName = dbName; // if (my_dbName.isEmpty()) { // const TQStringList& 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; TQString 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 TQString 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(TQString("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(TQString("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( TQString("%1.%2").arg(versionMajor()).arg(versionMinor()) ) .arg( TQString("%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 TQValueList::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 ? TQString::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; } TQString Connection::currentDatabase() const { return d->usedDatabase; } bool Connection::useTemporaryDatabaseIfNeeded(TQString &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 TQString &dbName ) { if (!checkConnected()) return false; TQString 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 = TQFileInfo(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; } TQString 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; } TQStringList Connection::objectNames(int objType, bool* ok) { TQStringList list; if (!checkIsDatabaseUsed()) { if(ok) *ok = false; return list; } TQString sql; if (objType==KexiDB::AnyObjectType) sql = "SELECT o_name FROM kexi__objects"; else sql = TQString::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()) { TQString 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; } TQStringList Connection::tableNames(bool also_system_tables) { bool ok = true; TQStringList list = objectNames(TableObjectType, &ok); if (also_system_tables && ok) { list += Connection::kexiDBSystemTableNames(); } return list; } //! \todo (js): this will depend on KexiDB lib version const TQStringList& 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; } TQValueList Connection::tableIds() { return objectIds(KexiDB::TableObjectType); } TQValueList Connection::queryIds() { return objectIds(KexiDB::QueryObjectType); } TQValueList Connection::objectIds(int objType) { TQValueList list; if (!checkIsDatabaseUsed()) return list; Cursor *c = executeQuery( TQString::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()) { TQString 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; } TQString Connection::createTableStatement( const KexiDB::TableSchema& tableSchema ) const { // Each SQL identifier needs to be escaped in the generated query. TQString 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 += ", "; TQString 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 += TQString::fromLatin1("(%1,%2)").arg(field->precision()).arg(field->scale()); else v += TQString::fromLatin1("(%1)").arg(field->precision()); } else if (field->type()==Field::Text && field->length()>0) v += TQString::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()) { TQString valToSQL( m_driver->valueToSQL( field, field->defaultValue() ) ); if (!valToSQL.isEmpty()) //for sanity v += TQString::fromLatin1(" DEFAULT ") + valToSQL; } } sql += v; } sql += ")"; return sql; } //yeah, it is very efficient: #define C_A(a) , const TQVariant& 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 << "******** " << TQString("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) \ { \ TQString value; \ Field::List *flist = fields.fields(); \ vals \ return executeSQL( \ TQString("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, TQValueList& values) { // Each SQL identifier needs to be escaped in the generated query. Field::List *fields = tableSchema.fields(); Field *f = fields->first(); // TQString s_val; // s_val.reserve(4096); m_sql = TQString(); TQValueList::ConstIterator it = values.constBegin(); // int i=0; while (f && (it!=values.end())) { if (m_sql.isEmpty()) m_sql = TQString("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, TQValueList& 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; // TQString s_val; // s_val.reserve(4096); m_sql = TQString(); TQValueList::ConstIterator it = values.constBegin(); // int i=0; while (f && (it!=values.constEnd())) { if (m_sql.isEmpty()) m_sql = TQString("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 TQString& statement ) { m_sql = statement; //remember for error handling if (!drv_executeSQL( m_sql )) { m_errMsg = TQString(); //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; } TQString Connection::selectStatement( KexiDB::QuerySchema& querySchema, const TQValueList& params, const SelectStatementOptions& options) const { //"SELECT FROM ..." is theoretically allowed " //if (querySchema.fieldCount()<1) // return TQString(); // 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; } } } TQString sql; //final sql string sql.reserve(4096); //unused TQString s_from_additional; //additional tables list needed for lookup fields TQString s_additional_joins; //additional joins needed for lookup fields TQString 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; TQPtrList subqueries_for_lookup_data; // subqueries will be added to FROM section TQString kexidb_subquery_prefix("__kexidb_subquery_"); for (Field::ListIterator it = querySchema.fieldsIterator(); (f = it.current()); ++it, number++) { if (querySchema.isColumnVisible(number)) { if (!sql.isEmpty()) sql += TQString::fromLatin1(", "); if (f->isQueryAsterisk()) { if (!singleTable && static_cast(f)->isSingleTableAsterisk()) //single-table * sql += escapeIdentifier(f->table()->name(), options.identifierEscaping) + TQString::fromLatin1(".*"); else //all-tables * (or simplified table.* when there's only one table) sql += TQString::fromLatin1("*"); } else { if (f->isExpression()) { sql += f->expression()->toString(); } else { if (!f->table()) //sanity check return TQString(); TQString 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); } TQString aliasString = TQString(querySchema.columnAlias(number)); if (!aliasString.isEmpty()) sql += (TQString::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 += TQString::fromLatin1(" "); TQString internalUniqueTableAlias( TQString("__kexidb_") + lookupTable->name() + "_" + TQString::number(internalUniqueTableAliasNumber++) ); s_additional_joins += TQString("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 += TQString::fromLatin1(", "); s_from_additional += escapeIdentifier(visibleField->table()->name(), options.identifierEscaping); */ } #endif if (!s_additional_fields.isEmpty()) s_additional_fields += TQString::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 TQString(); } const QueryColumnInfo::Vector fieldsExpanded( lookupQuery->fieldsExpanded() ); if ((uint)lookupFieldSchema->boundColumn() >= fieldsExpanded.count()) { KexiDBWarn << "Connection::selectStatement(): (uint)lookupFieldSchema->boundColumn() >= fieldsExpanded.count()" << endl; return TQString(); } QueryColumnInfo *boundColumnInfo = fieldsExpanded.at( lookupFieldSchema->boundColumn() ); if (!boundColumnInfo) { KexiDBWarn << "Connection::selectStatement(): !boundColumnInfo" << endl; return TQString(); } Field *boundField = boundColumnInfo->field; if (!boundField) { KexiDBWarn << "Connection::selectStatement(): !boundField" << endl; return TQString(); } //add LEFT OUTER JOIN if (!s_additional_joins.isEmpty()) s_additional_joins += TQString::fromLatin1(" "); TQString internalUniqueQueryAlias( kexidb_subquery_prefix + lookupQuery->name() + "_" + TQString::number(internalUniqueQueryAliasNumber++) ); s_additional_joins += TQString("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 += TQString::fromLatin1(", "); const TQValueList visibleColumns( lookupFieldSchema->visibleColumns() ); TQString expression; foreach (TQValueList::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 TQString(); } 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 TQString(); } } } } //add lookup fields if (!s_additional_fields.isEmpty()) sql += (TQString::fromLatin1(", ") + s_additional_fields); if (options.alsoRetrieveROWID) { //append rowid column TQString s; if (!sql.isEmpty()) s = TQString::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 += TQString::fromLatin1(" FROM "); TQString s_from; if (tables) { TableSchema *table; number = 0; for (TableSchema::ListIterator it(*tables); (table = it.current()); ++it, number++) { if (!s_from.isEmpty()) s_from += TQString::fromLatin1(", "); s_from += escapeIdentifier(table->name(), options.identifierEscaping); TQString aliasString = TQString(querySchema.tableAlias(number)); if (!aliasString.isEmpty()) s_from += (TQString::fromLatin1(" AS ") + aliasString); } /*unused if (!s_from_additional.isEmpty()) {//additional tables list needed for lookup fields if (!s_from.isEmpty()) s_from += TQString::fromLatin1(", "); s_from += s_from_additional; }*/ } // add subqueries for lookup data uint subqueries_for_lookup_data_counter = 0; for (TQPtrListIterator it(subqueries_for_lookup_data); subqueries_for_lookup_data.current(); ++it, subqueries_for_lookup_data_counter++) { if (!s_from.isEmpty()) s_from += TQString::fromLatin1(", "); s_from += TQString::fromLatin1("("); s_from += selectStatement( *it.current(), params, options ); s_from += TQString::fromLatin1(") AS %1%2") .arg(kexidb_subquery_prefix).arg(subqueries_for_lookup_data_counter); } sql += s_from; } TQString s_where; s_where.reserve(4096); //JOINS if (!s_additional_joins.isEmpty()) { sql += TQString::fromLatin1(" ") + s_additional_joins + TQString::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 += TQString::fromLatin1(" AND "); Field::Pair *pair; TQString s_where_sub; for (TQPtrListIterator p_it(*rel->fieldPairs()); (pair = p_it.current()); ++p_it) { if (!s_where_sub.isEmpty()) s_where_sub += TQString::fromLatin1(" AND "); s_where_sub += ( escapeIdentifier(pair->first->table()->name(), options.identifierEscaping) + TQString::fromLatin1(".") + escapeIdentifier(pair->first->name(), options.identifierEscaping) + TQString::fromLatin1(" = ") + escapeIdentifier(pair->second->table()->name(), options.identifierEscaping) + TQString::fromLatin1(".") + escapeIdentifier(pair->second->name(), options.identifierEscaping)); } if (rel->fieldPairs()->count()>1) { s_where_sub.prepend("("); s_where_sub += TQString::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 += TQString::fromLatin1(" WHERE ") + s_where; //! \todo (js) add other sql parts //(use wasWhere here) // ORDER BY TQString orderByString( querySchema.orderByColumnList().toSQLString(!singleTable/*includeTableName*/, driver(), options.identifierEscaping) ); const TQValueVector 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 (TQValueVector::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; } TQString 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; } TQ_ULLONG Connection::lastInsertedAutoIncValue(const TQString& aiFieldName, const TQString& tableName, TQ_ULLONG* ROWID) { TQ_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( TQString::fromLatin1("SELECT ") + tableName + TQString::fromLatin1(".") + aiFieldName + TQString::fromLatin1(" FROM ") + tableName + TQString::fromLatin1(" WHERE ") + m_driver->beh->ROW_ID_FIELD_NAME + TQString::fromLatin1("=") + TQString::number(row_id), rdata)) { // KexiDBDbg << "Connection::lastInsertedAutoIncValue(): row_id<=0 || true!=querySingleRecord()" << endl; return (TQ_ULLONG)-1; //ULL; } return rdata[0].toULongLong(); } TQ_ULLONG Connection::lastInsertedAutoIncValue(const TQString& aiFieldName, const KexiDB::TableSchema& table, TQ_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(TQValueList& vals, Field* f) { vals.clear(); vals << TQVariant(f->table()->id()) << TQVariant(f->type()) << TQVariant(f->name()) << TQVariant(f->isFPNumericType() ? f->scale() : f->length()) << TQVariant(f->isFPNumericType() ? f->precision() : 0) << TQVariant(f->constraints()) << TQVariant(f->options()) // KexiDB::variantToString() is needed here because the value can be of any TQVariant type, // depending on f->type() << (f->defaultValue().isNull() ? TQVariant() : TQVariant(KexiDB::variantToString( f->defaultValue() ))) << TQVariant(f->order()) << TQVariant(f->caption()) << TQVariant(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; TQValueList vals; buildValuesForKexi__Fields(vals, field); TQValueList::ConstIterator valsIt = vals.constBegin(); Field *f; bool first = true; TQString sql = "UPDATE kexi__fields SET "; for (Field::ListIterator it( fl->fieldsIterator() ); (f = it.current()); ++it, ++valsIt) { sql.append( (first ? TQString() : TQString(", ")) + f->name() + "=" + m_driver->valueToSQL( f, *valsIt ) ); if (first) first = false; } delete fl; sql.append(TQString(" WHERE t_id=") + TQString::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 TQString &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++*/) { TQValueList 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 TQString& 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; TQString 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 TQString& 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 TQString& 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 TQString oldTableName = tableSchema.name(); const TQString 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(TQString::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(TQString::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(TQString::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 TQString& newName) { const TQString oldTableName = tableSchema.name(); tableSchema.setName(newName); if (!executeSQL(TQString::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 TQString& 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 TQValueList& 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() { TQString 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 TQString& 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 TQValueList& 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, TQValueList(), 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 TQValueList& 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( TQString("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(TQString::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 TQString& objectName, SchemaData &sdata ) { RowData data; if (true!=querySingleRecord(TQString::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(TQString::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, TQVariant(sdata.type()), TQVariant(sdata.name()), TQVariant(sdata.caption()), TQVariant(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, TQVariant(sdata.id()), TQVariant(sdata.type()), TQVariant(sdata.name()), TQVariant(sdata.caption()), TQVariant(sdata.description()) )) ok = false; delete fl; return ok; } } //existing object: return executeSQL(TQString("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 TQString* 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 TQString& 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 TQString& sql, TQString &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 TQString& sql, int &number, uint column, bool addLimitTo1) { static TQString 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 TQString& sql, TQStringList& 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 TQString& 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 = TQString("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( TQDomDocument& doc, TQDomElement& extendedTableSchemaMainEl, bool& extendedTableSchemaStringIsEmpty) { if (!extendedTableSchemaStringIsEmpty) return; //init document extendedTableSchemaMainEl = doc.createElement("EXTENDED_TABLE_SCHEMA"); doc.appendChild( extendedTableSchemaMainEl ); extendedTableSchemaMainEl.setAttribute("version", TQString::number(KEXIDB_EXTENDED_TABLE_SCHEMA_VERSION)); extendedTableSchemaStringIsEmpty = false; } //! Used by addFieldPropertyToExtendedTableSchemaData() static void createExtendedTableSchemaFieldElementIfNeeded(TQDomDocument& doc, TQDomElement& extendedTableSchemaMainEl, const TQString& fieldName, TQDomElement& 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 TQVariant& propertyValue, TQDomDocument& doc, TQDomElement& extendedTableSchemaMainEl, TQDomElement& extendedTableSchemaFieldEl, bool& extendedTableSchemaStringIsEmpty, bool custom = false ) { createExtendedTableSchemaMainElementIfNeeded(doc, extendedTableSchemaMainEl, extendedTableSchemaStringIsEmpty); createExtendedTableSchemaFieldElementIfNeeded( doc, extendedTableSchemaMainEl, f->name(), extendedTableSchemaFieldEl); //create TQDomElement extendedTableSchemaFieldPropertyEl = doc.createElement("property"); extendedTableSchemaFieldEl.appendChild( extendedTableSchemaFieldPropertyEl ); if (custom) extendedTableSchemaFieldPropertyEl.setAttribute("custom", "true"); extendedTableSchemaFieldPropertyEl.setAttribute("name", propertyName); TQDomElement extendedTableSchemaFieldPropertyValueEl; switch (propertyValue.type()) { case TQVariant::String: extendedTableSchemaFieldPropertyValueEl = doc.createElement("string"); break; case TQVariant::CString: extendedTableSchemaFieldPropertyValueEl = doc.createElement("cstring"); break; case TQVariant::Int: case TQVariant::Double: case TQVariant::UInt: case TQVariant::LongLong: case TQVariant::ULongLong: extendedTableSchemaFieldPropertyValueEl = doc.createElement("number"); break; case TQVariant::Bool: extendedTableSchemaFieldPropertyValueEl = doc.createElement("bool"); break; default: //! @todo add more TQVariant 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 TQDomDocument doc("EXTENDED_TABLE_SCHEMA"); TQDomElement extendedTableSchemaMainEl; bool extendedTableSchemaStringIsEmpty = true; //for each field: Field *f; for (Field::ListIterator it( *tableSchema.fields() ); (f = it.current()); ++it) { TQDomElement 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(TQString("** Extended table schema REMOVED.")); #endif if (!removeDataBlock( tableSchema.id(), "extended_schema")) return false; } else { #ifdef KEXI_DEBUG_GUI KexiUtils::addAlterTableActionDebug(TQString("** 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) TQString 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; TQDomDocument doc; TQString 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 ); TQDomElement docEl = doc.documentElement(); if (docEl.tagName()!="EXTENDED_TABLE_SCHEMA") loadExtendedTableSchemaData_ERR3( extendedTableSchemaString ); for (TQDomNode n = docEl.firstChild(); !n.isNull(); n = n.nextSibling()) { TQDomElement 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 (TQDomNode propNode = fieldEl.firstChild(); !propNode.isNull(); propNode = propNode.nextSibling()) { TQDomElement propEl = propNode.toElement(); bool ok; int intValue; if (propEl.tagName()=="property") { TQCString 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 = TQMAX( 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( TQString::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 TQString& tableName ) { TQString m_tableName = tableName.lower(); TableSchema *t = d->tables_byname[m_tableName]; if (t) return t; //not found: retrieve schema RowData data; if (true!=querySingleRecord(TQString::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(TQString::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, TQString &dataString, const TQString& dataID ) { if (objectID<=0) return false; return querySingleString( TQString("SELECT o_data FROM kexi__objectdata WHERE o_id=") + TQString::number(objectID) + " AND " + KexiDB::sqlWhere(m_driver, KexiDB::Field::Text, "o_sub_id", dataID), dataString ); } bool Connection::storeDataBlock( int objectID, const TQString &dataString, const TQString& dataID ) { if (objectID<=0) return false; TQString sql(TQString::fromLatin1("SELECT kexi__objectdata.o_id FROM kexi__objectdata WHERE o_id=%1").arg(objectID)); TQString 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=" + TQString::number(objectID) + " AND " + sql_sub ); } return executeSQL( TQString::fromLatin1("INSERT INTO kexi__objectdata (o_id, o_data, o_sub_id) VALUES (") + TQString::number(objectID) +"," + m_driver->valueToSQL( KexiDB::Field::LongText, dataString ) + "," + m_driver->valueToSQL( KexiDB::Field::Text, dataID ) + ")" ); } bool Connection::removeDataBlock( int objectID, const TQString& dataID) { if (objectID<=0) return false; if (dataID.isEmpty()) return KexiDB::deleteRow(*this, "kexi__objectdata", "o_id", TQString::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 0; TQString 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 TQString& queryName ) { TQString m_queryName = queryName.lower(); QuerySchema *q = d->queries_byname[m_queryName]; if (q) return q; //not found: retrieve schema RowData data; if (true!=querySingleRecord(TQString::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(TQString::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 TQString& 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 TQString& tsname) { TableSchema *ts = new TableSchema(tsname.lower()); insertInternalTableSchema( ts ); return ts; } bool Connection::isInternalTableSchema(const TQString& 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()); } } TQString Connection::anyAvailableDatabaseName() { if (!d->availableDatabaseName.isEmpty()) { return d->availableDatabaseName; } return m_driver->beh->ALWAYS_AVAILABLE_DATABASE_NAME; } void Connection::setAvailableDatabaseName(const TQString& dbName) { d->availableDatabaseName = dbName; } //! @internal used in updateRow(), insertRow(), inline void updateRowDataWithNewValues(QuerySchema &query, RowData& data, KexiDB::RowEditBuffer::DBMap& b, TQMap& columnsOrderExpanded) { columnsOrderExpanded = query.columnsOrder(QuerySchema::ExpandedList); TQMap::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 "; TQString 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 TQValueVector 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 "; TQVariant 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: TQMap 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; TQString 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 TQValueVector 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,TQVariant()/*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: TQMap columnsOrderExpanded; updateRowDataWithNewValues(query, data, b, columnsOrderExpanded); //fetch autoincremented values QueryColumnInfo::List *aif_list = query.autoIncrementFields(); TQ_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? TQ_ULLONG last_id = lastInsertedAutoIncValue( id_columnInfo->field->name(), id_columnInfo->field->table()->name(), &ROWID); if (last_id==(TQ_ULLONG)-1 || last_id<=0) { //! @todo show error //! @todo remove just inserted row. How? Using ROLLBACK? return false; } RowData aif_data; TQString getAutoIncForInsertedValue = TQString::fromLatin1("SELECT ") + query.autoIncrementSQLFieldsList(m_driver) + TQString::fromLatin1(" FROM ") + escapeIdentifier(id_columnInfo->field->table()->name()) + TQString::fromLatin1(" WHERE ") + escapeIdentifier(id_columnInfo->field->name()) + "=" + TQString::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 "; TQString sqlwhere; sqlwhere.reserve(1024); if (pkey) { const TQValueVector 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 "; TQVariant 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) { TQPtrList* listeners = d->tableSchemaChangeListeners[&schema]; if (!listeners) { listeners = new TQPtrList(); d->tableSchemaChangeListeners.insert(&schema, listeners); } //TODO: inefficient if (listeners->findRef( &listener )==-1) listeners->append( &listener ); } void Connection::unregisterForTableSchemaChanges(TableSchemaChangeListenerInterface& listener, TableSchema &schema) { TQPtrList* listeners = d->tableSchemaChangeListeners[&schema]; if (!listeners) return; //TODO: inefficient listeners->remove( &listener ); } void Connection::unregisterForTablesSchemaChanges(TableSchemaChangeListenerInterface& listener) { for (TQPtrDictIterator< TQPtrList > it(d->tableSchemaChangeListeners); it.current(); ++it) { if (-1!=it.current()->find(&listener)) it.current()->take(); } } TQPtrList* Connection::tableSchemaChangeListeners(TableSchema& tableSchema) const { KexiDBDbg << d->tableSchemaChangeListeners.count() << endl; return d->tableSchemaChangeListeners[&tableSchema]; } tristate Connection::closeAllTableSchemaChangeListeners(TableSchema& tableSchema) { TQPtrList *listeners = d->tableSchemaChangeListeners[&tableSchema]; if (!listeners) return true; TQPtrListIterator tmpListeners(*listeners); //safer copy tristate res = true; //try to close every window for (TQPtrListIterator 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"