/* This file is part of the KDE project Copyright (C) 2006-2007 Jaroslaw Staniek This library 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 library 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 library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "alter.h" #include "utils.h" #include #include #include #include namespace KexiDB { class AlterTableHandler::Private { public: Private() {} ~Private() {} ActionList actions; QGuardedPtr conn; }; } using namespace KexiDB; //! a global instance used to when returning null is needed AlterTableHandler::ChangeFieldPropertyAction nullChangeFieldPropertyAction(true); AlterTableHandler::RemoveFieldAction nullRemoveFieldAction(true); AlterTableHandler::InsertFieldAction nullInsertFieldAction(true); AlterTableHandler::MoveFieldPositionAction nullMoveFieldPositionAction(true); //-------------------------------------------------------- AlterTableHandler::ActionBase::ActionBase(bool null) : m_alteringRequirements(0) , m_order(-1) , m_null(null) { } AlterTableHandler::ActionBase::~ActionBase() { } AlterTableHandler::ChangeFieldPropertyAction& AlterTableHandler::ActionBase::toChangeFieldPropertyAction() { if (dynamic_cast(this)) return *dynamic_cast(this); return nullChangeFieldPropertyAction; } AlterTableHandler::RemoveFieldAction& AlterTableHandler::ActionBase::toRemoveFieldAction() { if (dynamic_cast(this)) return *dynamic_cast(this); return nullRemoveFieldAction; } AlterTableHandler::InsertFieldAction& AlterTableHandler::ActionBase::toInsertFieldAction() { if (dynamic_cast(this)) return *dynamic_cast(this); return nullInsertFieldAction; } AlterTableHandler::MoveFieldPositionAction& AlterTableHandler::ActionBase::toMoveFieldPositionAction() { if (dynamic_cast(this)) return *dynamic_cast(this); return nullMoveFieldPositionAction; } //-------------------------------------------------------- AlterTableHandler::FieldActionBase::FieldActionBase(const QString& fieldName, int uid) : ActionBase() , m_fieldUID(uid) , m_fieldName(fieldName) { } AlterTableHandler::FieldActionBase::FieldActionBase(bool) : ActionBase(true) , m_fieldUID(-1) { } AlterTableHandler::FieldActionBase::~FieldActionBase() { } //-------------------------------------------------------- static KStaticDeleter< QMap > KexiDB_alteringTypeForProperty_deleter; QMap *KexiDB_alteringTypeForProperty = 0; int AlterTableHandler::alteringTypeForProperty(const QCString& propertyName) { if (!KexiDB_alteringTypeForProperty) { KexiDB_alteringTypeForProperty_deleter.setObject( KexiDB_alteringTypeForProperty, new QMap() ); #define I(name, type) \ KexiDB_alteringTypeForProperty->insert(QCString(name).lower(), (int)AlterTableHandler::type) #define I2(name, type1, type2) \ flag = (int)AlterTableHandler::type1|(int)AlterTableHandler::type2; \ if (flag & AlterTableHandler::PhysicalAlteringRequired) \ flag |= AlterTableHandler::MainSchemaAlteringRequired; \ KexiDB_alteringTypeForProperty->insert(QCString(name).lower(), flag) /* useful links: http://dev.mysql.com/doc/refman/5.0/en/create-table.html */ // ExtendedSchemaAlteringRequired is here because when the field is renamed, // we need to do the same rename in extended table schema: int flag; I2("name", PhysicalAlteringRequired, MainSchemaAlteringRequired); I2("type", PhysicalAlteringRequired, DataConversionRequired); I("caption", MainSchemaAlteringRequired); I("description", MainSchemaAlteringRequired); I2("unsigned", PhysicalAlteringRequired, DataConversionRequired); // always? I2("length", PhysicalAlteringRequired, DataConversionRequired); // always? I2("precision", PhysicalAlteringRequired, DataConversionRequired); // always? I("width", MainSchemaAlteringRequired); // defaultValue: depends on backend, for mysql it can only by a constant or now()... // -- should we look at Driver here? #ifdef KEXI_NO_UNFINISHED //! @todo reenable I("defaultValue", MainSchemaAlteringRequired); #else I2("defaultValue", PhysicalAlteringRequired, MainSchemaAlteringRequired); #endif I2("primaryKey", PhysicalAlteringRequired, DataConversionRequired); I2("unique", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here I2("notNull", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here // allowEmpty: only support it just at kexi level? maybe there is a backend that supports this? I2("allowEmpty", PhysicalAlteringRequired, MainSchemaAlteringRequired); I2("autoIncrement", PhysicalAlteringRequired, DataConversionRequired); // data conversion may be hard here I2("indexed", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here // easier cases follow... I("visibleDecimalPlaces", ExtendedSchemaAlteringRequired); // lookup-field-related properties... /*moved to KexiDB::isExtendedTableFieldProperty() I("boundColumn", ExtendedSchemaAlteringRequired); I("rowSource", ExtendedSchemaAlteringRequired); I("rowSourceType", ExtendedSchemaAlteringRequired); I("rowSourceValues", ExtendedSchemaAlteringRequired); I("visibleColumn", ExtendedSchemaAlteringRequired); I("columnWidths", ExtendedSchemaAlteringRequired); I("showColumnHeaders", ExtendedSchemaAlteringRequired); I("listRows", ExtendedSchemaAlteringRequired); I("limitToList", ExtendedSchemaAlteringRequired); I("displayWidget", ExtendedSchemaAlteringRequired);*/ //more to come... #undef I #undef I2 } const int res = (*KexiDB_alteringTypeForProperty)[propertyName.lower()]; if (res == 0) { if (KexiDB::isExtendedTableFieldProperty(propertyName)) return (int)ExtendedSchemaAlteringRequired; KexiDBWarn << QString("AlterTableHandler::alteringTypeForProperty(): property \"%1\" not found!") .arg(propertyName) << endl; } return res; } //--- AlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction( const QString& fieldName, const QString& propertyName, const QVariant& newValue, int uid) : FieldActionBase(fieldName, uid) , m_propertyName(propertyName) , m_newValue(newValue) { } AlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction(bool) : FieldActionBase(true) { } AlterTableHandler::ChangeFieldPropertyAction::~ChangeFieldPropertyAction() { } void AlterTableHandler::ChangeFieldPropertyAction::updateAlteringRequirements() { // m_alteringRequirements = ???; setAlteringRequirements( alteringTypeForProperty( m_propertyName.latin1() ) ); } QString AlterTableHandler::ChangeFieldPropertyAction::debugString(const DebugOptions& debugOptions) { QString s = QString("Set \"%1\" property for table field \"%2\" to \"%3\"") .arg(m_propertyName).arg(fieldName()).arg(m_newValue.toString()); if (debugOptions.showUID) s.append(QString(" (UID=%1)").arg(m_fieldUID)); return s; } static AlterTableHandler::ActionDict* createActionDict( AlterTableHandler::ActionDictDict &fieldActions, int forFieldUID ) { AlterTableHandler::ActionDict* dict = new AlterTableHandler::ActionDict(101, false); dict->setAutoDelete(true); fieldActions.insert( forFieldUID, dict ); return dict; } static void debugAction(AlterTableHandler::ActionBase *action, int nestingLevel, bool simulate, const QString& prependString = QString::null, QString* debugTarget = 0) { QString debugString; if (!debugTarget) debugString = prependString; if (action) { AlterTableHandler::ActionBase::DebugOptions debugOptions; debugOptions.showUID = debugTarget==0; debugOptions.showFieldDebug = debugTarget!=0; debugString += action->debugString( debugOptions ); } else { if (!debugTarget) debugString += "[No action]"; //hmm } if (debugTarget) { if (!debugString.isEmpty()) *debugTarget += debugString + '\n'; } else { KexiDBDbg << debugString << endl; #ifdef KEXI_DEBUG_GUI if (simulate) KexiUtils::addAlterTableActionDebug(debugString, nestingLevel); #endif } } static void debugActionDict(AlterTableHandler::ActionDict *dict, int fieldUID, bool simulate) { QString fieldName; AlterTableHandler::ActionDictIterator it(*dict); if (dynamic_cast(it.current())) //retrieve field name from the 1st related action fieldName = dynamic_cast(it.current())->fieldName(); else fieldName = "??"; QString dbg = QString("Action dict for field \"%1\" (%2, UID=%3):") .arg(fieldName).arg(dict->count()).arg(fieldUID); KexiDBDbg << dbg << endl; #ifdef KEXI_DEBUG_GUI if (simulate) KexiUtils::addAlterTableActionDebug(dbg, 1); #endif for (;it.current(); ++it) { debugAction(it.current(), 2, simulate); } } static void debugFieldActions(const AlterTableHandler::ActionDictDict &fieldActions, bool simulate) { #ifdef KEXI_DEBUG_GUI if (simulate) KexiUtils::addAlterTableActionDebug("** Simplified Field Actions:"); #endif for (AlterTableHandler::ActionDictDictIterator it(fieldActions); it.current(); ++it) { debugActionDict(it.current(), it.currentKey(), simulate); } } /*! Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. Case 1. (special) when new action=[rename A to B] and exists=[rename B to C] => remove [rename B to C] and set result to new [rename A to C] and go to 1b. Case 1b. when new action=[rename A to B] and actions exist like [set property P to C in field B] or like [delete field B] or like [move field B] => change B to A for all these actions Case 2. when new action=[change property in field A] (property != name) and exists=[remove A] or exists=[change property in field A] => do not add [change property in field A] because it will be removed anyway or the property will change */ void AlterTableHandler::ChangeFieldPropertyAction::simplifyActions(ActionDictDict &fieldActions) { ActionDict *actionsLikeThis = fieldActions[ uid() ]; if (m_propertyName=="name") { // Case 1. special: name1 -> name2, i.e. rename action QString newName( newValue().toString() ); // try to find rename(newName, otherName) action ActionBase *renameActionLikeThis = actionsLikeThis ? actionsLikeThis->find( "name" ) : 0; if (dynamic_cast(renameActionLikeThis)) { // 1. instead of having rename(fieldName(), newValue()) action, // let's have rename(fieldName(), otherName) action dynamic_cast(renameActionLikeThis)->m_newValue = dynamic_cast(renameActionLikeThis)->m_newValue; /* AlterTableHandler::ChangeFieldPropertyAction* newRenameAction = new AlterTableHandler::ChangeFieldPropertyAction( *this ); newRenameAction->m_newValue = dynamic_cast(renameActionLikeThis)->m_newValue; // (m_order is the same as in newAction) // replace prev. rename action (if any) actionsLikeThis->remove( "name" ); ActionDict *adict = fieldActions[ fieldName().latin1() ]; if (!adict) adict = createActionDict( fieldActions, fieldName() ); adict->insert(m_propertyName.latin1(), newRenameAction);*/ } else { ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->find( ":remove:" ) : 0; if (removeActionForThisField) { //if this field is going to be removed, jsut change the action's field name // and do not add a new action } else { //just insert a copy of the rename action if (!actionsLikeThis) actionsLikeThis = createActionDict( fieldActions, uid() ); //fieldName() ); AlterTableHandler::ChangeFieldPropertyAction* newRenameAction = new AlterTableHandler::ChangeFieldPropertyAction( *this ); KexiDBDbg << "ChangeFieldPropertyAction::simplifyActions(): insert into '" << fieldName() << "' dict:" << newRenameAction->debugString() << endl; actionsLikeThis->insert( m_propertyName.latin1(), newRenameAction ); return; } } if (actionsLikeThis) { // Case 1b. change "field name" information to fieldName() in any action that // is related to newName // e.g. if there is setCaption("B", "captionA") action after rename("A","B"), // replace setCaption action with setCaption("A", "captionA") foreach_dict (ActionDictIterator, it, *actionsLikeThis) { dynamic_cast(it.current())->setFieldName( fieldName() ); } } return; } ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->find( ":remove:" ) : 0; if (removeActionForThisField) { //if this field is going to be removed, do not add a new action return; } // Case 2. other cases: just give up with adding this "intermediate" action // so, e.g. [ setCaption(A, "captionA"), setCaption(A, "captionB") ] // becomes: [ setCaption(A, "captionB") ] // because adding this action does nothing ActionDict *nextActionsLikeThis = fieldActions[ uid() ]; //fieldName().latin1() ]; if (!nextActionsLikeThis || !nextActionsLikeThis->find( m_propertyName.latin1() )) { //no such action, add this AlterTableHandler::ChangeFieldPropertyAction* newAction = new AlterTableHandler::ChangeFieldPropertyAction( *this ); if (!nextActionsLikeThis) nextActionsLikeThis = createActionDict( fieldActions, uid() );//fieldName() ); nextActionsLikeThis->insert( m_propertyName.latin1(), newAction ); } } bool AlterTableHandler::ChangeFieldPropertyAction::shouldBeRemoved(ActionDictDict &fieldActions) { Q_UNUSED(fieldActions); return fieldName().lower() == m_newValue.toString().lower(); } tristate AlterTableHandler::ChangeFieldPropertyAction::updateTableSchema(TableSchema &table, Field* field, QMap& fieldMap) { //1. Simpler cases first: changes that do not affect table schema at all // "caption", "description", "width", "visibleDecimalPlaces" if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.latin1())) { bool result = KexiDB::setFieldProperty(*field, m_propertyName.latin1(), newValue()); return result; } if (m_propertyName=="name") { if (fieldMap[ field->name() ] == field->name()) fieldMap.remove( field->name() ); fieldMap.insert( newValue().toString(), field->name() ); table.renameField(field, newValue().toString()); return true; } return cancelled; } /*! Many of the properties must be applied using a separate algorithm. */ tristate AlterTableHandler::ChangeFieldPropertyAction::execute(Connection &conn, TableSchema &table) { Q_UNUSED(conn); Field *field = table.field( fieldName() ); if (!field) { //! @todo errmsg return false; } bool result; //1. Simpler cases first: changes that do not affect table schema at all // "caption", "description", "width", "visibleDecimalPlaces" if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.latin1())) { result = KexiDB::setFieldProperty(*field, m_propertyName.latin1(), newValue()); return result; } //todo return true; //2. Harder cases, that often require special care if (m_propertyName=="name") { /*mysql: A. Get real field type (it's safer): let be the 2nd "Type" column from result of "DESCRIBE tablename oldfieldname" ( http://dev.mysql.com/doc/refman/5.0/en/describe.html ) B. Run "ALTER TABLE tablename CHANGE oldfieldname newfieldname "; ( http://dev.mysql.com/doc/refman/5.0/en/alter-table.html ) */ } if (m_propertyName=="type") { /*mysql: A. Like A. for "name" property above B. Construct string, eg. "varchar(50)" using the driver C. Like B. for "name" property above (mysql then truncate the values for changes like varchar -> integer, and properly convert the values for changes like integer -> varchar) TODO: more cases to check */ } if (m_propertyName=="length") { //use "select max( length(o_name) ) from kexi__Objects" } if (m_propertyName=="primaryKey") { //! @todo } /* "name", "unsigned", "precision", "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty", "autoIncrement", "indexed", bool result = KexiDB::setFieldProperty(*field, m_propertyName.latin1(), newValue()); */ return result; } //-------------------------------------------------------- AlterTableHandler::RemoveFieldAction::RemoveFieldAction(const QString& fieldName, int uid) : FieldActionBase(fieldName, uid) { } AlterTableHandler::RemoveFieldAction::RemoveFieldAction(bool) : FieldActionBase(true) { } AlterTableHandler::RemoveFieldAction::~RemoveFieldAction() { } void AlterTableHandler::RemoveFieldAction::updateAlteringRequirements() { //! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ? setAlteringRequirements( PhysicalAlteringRequired ); //! @todo } QString AlterTableHandler::RemoveFieldAction::debugString(const DebugOptions& debugOptions) { QString s = QString("Remove table field \"%1\"").arg(fieldName()); if (debugOptions.showUID) s.append(QString(" (UID=%1)").arg(uid())); return s; } /*! Legend: A,B==objects, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. Preconditions: we assume there cannot be such case encountered: ([remove A], [do something related on A]) (except for [remove A], [insert A]) General Case: it's safe to always insert a [remove A] action. */ void AlterTableHandler::RemoveFieldAction::simplifyActions(ActionDictDict &fieldActions) { //! @todo not checked AlterTableHandler::RemoveFieldAction* newAction = new AlterTableHandler::RemoveFieldAction( *this ); ActionDict *actionsLikeThis = fieldActions[ uid() ]; //fieldName().latin1() ]; if (!actionsLikeThis) actionsLikeThis = createActionDict( fieldActions, uid() ); //fieldName() ); actionsLikeThis->insert( ":remove:", newAction ); //special } tristate AlterTableHandler::RemoveFieldAction::updateTableSchema(TableSchema &table, Field* field, QMap& fieldMap) { fieldMap.remove( field->name() ); table.removeField(field); return true; } tristate AlterTableHandler::RemoveFieldAction::execute(Connection& conn, TableSchema& table) { Q_UNUSED(conn); Q_UNUSED(table); //! @todo return true; } //-------------------------------------------------------- AlterTableHandler::InsertFieldAction::InsertFieldAction(int fieldIndex, KexiDB::Field *field, int uid) : FieldActionBase(field->name(), uid) , m_index(fieldIndex) , m_field(0) { Q_ASSERT(field); setField(field); } AlterTableHandler::InsertFieldAction::InsertFieldAction(const InsertFieldAction& action) : FieldActionBase(action) //action.fieldName(), action.uid()) , m_index(action.index()) { m_field = new KexiDB::Field( action.field() ); } AlterTableHandler::InsertFieldAction::InsertFieldAction(bool) : FieldActionBase(true) , m_index(0) , m_field(0) { } AlterTableHandler::InsertFieldAction::~InsertFieldAction() { delete m_field; } void AlterTableHandler::InsertFieldAction::setField(KexiDB::Field* field) { if (m_field) delete m_field; m_field = field; setFieldName(m_field ? m_field->name() : QString::null); } void AlterTableHandler::InsertFieldAction::updateAlteringRequirements() { //! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ? setAlteringRequirements( PhysicalAlteringRequired ); //! @todo } QString AlterTableHandler::InsertFieldAction::debugString(const DebugOptions& debugOptions) { QString s = QString("Insert table field \"%1\" at position %2") .arg(m_field->name()).arg(m_index); if (debugOptions.showUID) s.append(QString(" (UID=%1)").arg(m_fieldUID)); if (debugOptions.showFieldDebug) s.append(QString(" (%1)").arg(m_field->debugString())); return s; } /*! Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. Case 1: there are "change property" actions after the Insert action. -> change the properties in the Insert action itself and remove the "change property" actions. Examples: [Insert A] && [rename A to B] => [Insert B] [Insert A] && [change property P in field A] => [Insert A with P altered] Comment: we need to do this reduction because otherwise we'd need to do psyhical altering right after [Insert A] if [rename A to B] follows. */ void AlterTableHandler::InsertFieldAction::simplifyActions(ActionDictDict &fieldActions) { // Try to find actions related to this action ActionDict *actionsForThisField = fieldActions[ uid() ]; //m_field->name().latin1() ]; ActionBase *removeActionForThisField = actionsForThisField ? actionsForThisField->find( ":remove:" ) : 0; if (removeActionForThisField) { //if this field is going to be removed, do not add a new action //and remove the "Remove" action actionsForThisField->remove(":remove:"); return; } if (actionsForThisField) { //collect property values that have to be changed in this field QMap values; for (ActionDictIterator it(*actionsForThisField); it.current();) { ChangeFieldPropertyAction* changePropertyAction = dynamic_cast(it.current()); if (changePropertyAction) { //if this field is going to be renamed, also update fieldName() if (changePropertyAction->propertyName()=="name") { setFieldName(changePropertyAction->newValue().toString()); } values.insert( changePropertyAction->propertyName().latin1(), changePropertyAction->newValue() ); //the subsequent "change property" action is no longer needed actionsForThisField->remove(changePropertyAction->propertyName().latin1()); } else { ++it; } } if (!values.isEmpty()) { //update field, so it will be created as one step KexiDB::Field *f = new KexiDB::Field( field() ); if (KexiDB::setFieldProperties( *f, values )) { //field() = f; setField( f ); field().debug(); #ifdef KEXI_DEBUG_GUI KexiUtils::addAlterTableActionDebug( QString("** Property-set actions moved to field definition itself:\n")+field().debugString(), 0); #endif } else { #ifdef KEXI_DEBUG_GUI KexiUtils::addAlterTableActionDebug( QString("** Failed to set properties for field ")+field().debugString(), 0); #endif KexiDBWarn << "AlterTableHandler::InsertFieldAction::simplifyActions(): KexiDB::setFieldProperties() failed!" << endl; delete f; } } } //ok, insert this action //! @todo not checked AlterTableHandler::InsertFieldAction* newAction = new AlterTableHandler::InsertFieldAction( *this ); if (!actionsForThisField) actionsForThisField = createActionDict( fieldActions, uid() ); actionsForThisField->insert( ":insert:", newAction ); //special } tristate AlterTableHandler::InsertFieldAction::updateTableSchema(TableSchema &table, Field* field, QMap& fieldMap) { //in most cases we won't add the field to fieldMap Q_UNUSED(field); //! @todo add it only when there should be fixed value (e.g. default) set for this new field... fieldMap.remove( this->field().name() ); table.insertField(index(), new Field(this->field())); return true; } tristate AlterTableHandler::InsertFieldAction::execute(Connection& conn, TableSchema& table) { Q_UNUSED(conn); Q_UNUSED(table); //! @todo return true; } //-------------------------------------------------------- AlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction( int fieldIndex, const QString& fieldName, int uid) : FieldActionBase(fieldName, uid) , m_index(fieldIndex) { } AlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(bool) : FieldActionBase(true) { } AlterTableHandler::MoveFieldPositionAction::~MoveFieldPositionAction() { } void AlterTableHandler::MoveFieldPositionAction::updateAlteringRequirements() { setAlteringRequirements( MainSchemaAlteringRequired ); //! @todo } QString AlterTableHandler::MoveFieldPositionAction::debugString(const DebugOptions& debugOptions) { QString s = QString("Move table field \"%1\" to position %2") .arg(fieldName()).arg(m_index); if (debugOptions.showUID) s.append(QString(" (UID=%1)").arg(uid())); return s; } void AlterTableHandler::MoveFieldPositionAction::simplifyActions(ActionDictDict &fieldActions) { Q_UNUSED(fieldActions); //! @todo } tristate AlterTableHandler::MoveFieldPositionAction::execute(Connection& conn, TableSchema& table) { Q_UNUSED(conn); Q_UNUSED(table); //! @todo return true; } //-------------------------------------------------------- AlterTableHandler::AlterTableHandler(Connection &conn) : Object() , d( new Private() ) { d->conn = &conn; } AlterTableHandler::~AlterTableHandler() { delete d; } void AlterTableHandler::addAction(ActionBase* action) { d->actions.append(action); } AlterTableHandler& AlterTableHandler::operator<< ( ActionBase* action ) { d->actions.append(action); return *this; } const AlterTableHandler::ActionList& AlterTableHandler::actions() const { return d->actions; } void AlterTableHandler::removeAction(int index) { d->actions.remove( d->actions.at(index) ); } void AlterTableHandler::clear() { d->actions.clear(); } void AlterTableHandler::setActions(const ActionList& actions) { d->actions = actions; } void AlterTableHandler::debug() { KexiDBDbg << "AlterTableHandler's actions:" << endl; foreach_list (ActionListIterator, it, d->actions) it.current()->debug(); } TableSchema* AlterTableHandler::execute(const QString& tableName, ExecutionArguments& args) { args.result = false; if (!d->conn) { //! @todo err msg? return 0; } if (d->conn->isReadOnly()) { //! @todo err msg? return 0; } if (!d->conn->isDatabaseUsed()) { //! @todo err msg? return 0; } TableSchema *oldTable = d->conn->tableSchema(tableName); if (!oldTable) { //! @todo err msg? return 0; } if (!args.debugString) debug(); // Find a sum of requirements... int allActionsCount = 0; for(ActionListIterator it(d->actions); it.current(); ++it, allActionsCount++) { it.current()->updateAlteringRequirements(); it.current()->m_order = allActionsCount; } /* Simplify actions list if possible and check for errors How to do it? - track property changes/deletions in reversed order - reduce intermediate actions Trivial example 1: *action1: "rename field a to b" *action2: "rename field b to c" *action3: "rename field c to d" After reduction: *action1: "rename field a to d" Summing up: we have tracked what happens to field curently named "d" and eventually discovered that it was originally named "a". Trivial example 2: *action1: "rename field a to b" *action2: "rename field b to c" *action3: "remove field b" After reduction: *action3: "remove field b" Summing up: we have noticed that field "b" has beed eventually removed so we needed to find all actions related to this field and remove them. This is good optimization, as some of the eventually removed actions would be difficult to perform and/or costly, what would be a waste of resources and a source of unwanted questions sent to the user. */ ActionListIterator it(d->actions); // Fields-related actions. ActionDictDict fieldActions(3001); fieldActions.setAutoDelete(true); ActionBase* action; for(it.toLast(); (action = it.current()); --it) { action->simplifyActions( fieldActions ); } if (!args.debugString) debugFieldActions(fieldActions, args.simulate); // Prepare actions for execution ---- // - Sort actions by order ActionVector actionsVector(allActionsCount); int currentActionsCount = 0; //some actions may be removed args.requirements = 0; QDict fieldsWithChangedMainSchema(997); // Used to collect fields with changed main schema. // This will be used when recreateTable is false to update kexi__fields for (ActionDictDictIterator it(fieldActions); it.current(); ++it) { for (AlterTableHandler::ActionDictIterator it2(*it.current());it2.current(); ++it2, currentActionsCount++) { if (it2.current()->shouldBeRemoved(fieldActions)) continue; actionsVector.insert( it2.current()->m_order, it2.current() ); // a sum of requirements... const int r = it2.current()->alteringRequirements(); args.requirements |= r; if (r & MainSchemaAlteringRequired && dynamic_cast(it2.current())) { // Remember, this will be used when recreateTable is false to update kexi__fields, below. fieldsWithChangedMainSchema.insert( dynamic_cast(it2.current())->fieldName(), (char*)1 ); } } } // - Debug QString dbg = QString("** Overall altering requirements: %1").arg(args.requirements); KexiDBDbg << dbg << endl; if (args.onlyComputeRequirements) { args.result = true; return 0; } const bool recreateTable = (args.requirements & PhysicalAlteringRequired); #ifdef KEXI_DEBUG_GUI if (args.simulate) KexiUtils::addAlterTableActionDebug(dbg, 0); #endif dbg = QString("** Ordered, simplified actions (%1, was %2):").arg(currentActionsCount).arg(allActionsCount); KexiDBDbg << dbg << endl; #ifdef KEXI_DEBUG_GUI if (args.simulate) KexiUtils::addAlterTableActionDebug(dbg, 0); #endif for (int i=0; iname()).arg(QString::number(rand(), 16)).arg(QString::number(rand(), 16)); if (!d->conn->tableSchema(tempDestTableName)) break; } newTable->setName( tempDestTableName ); } oldTable->debug(); if (recreateTable && !args.debugString) newTable->debug(); // Update table schema in memory ---- int lastUID = -1; Field *currentField = 0; QMap fieldMap; // a map from new value to old value foreach_list( Field::ListIterator, it, newTable->fieldsIterator() ) { fieldMap.insert( it.current()->name(), it.current()->name() ); } for (int i=0; i(action); if (!fieldAction) { currentField = 0; } else { if (lastUID != fieldAction->uid()) { currentField = newTable->field( fieldAction->fieldName() ); lastUID = currentField ? fieldAction->uid() : -1; } InsertFieldAction *insertFieldAction = dynamic_cast(action); if (insertFieldAction && insertFieldAction->index()>(int)newTable->fieldCount()) { //update index: there can be empty rows insertFieldAction->setIndex(newTable->fieldCount()); } } //if (!currentField) // continue; args.result = action->updateTableSchema(*newTable, currentField, fieldMap); if (args.result!=true) { if (recreateTable) delete newTable; return 0; } } if (recreateTable) { // Create the destination table with temporary name if (!d->conn->createTable( newTable, false )) { setError(d->conn); delete newTable; args.result = false; return 0; } } #if 0//todo // Execute actions ---- for (int i=0; iexecute(*d->conn, *newTable); if (!args.result || ~args.result) { //! @todo delete newTable... args.result = false; return 0; } } #endif // update extended table schema after executing the actions if (!d->conn->storeExtendedTableSchemaData(*newTable)) { //! @todo better errmsg? setError(d->conn); //! @todo delete newTable... args.result = false; return 0; } if (recreateTable) { // Copy the data: // Build "INSERT INTO ... SELECT FROM ..." SQL statement // The order is based on the order of the source table fields. // Notes: // -Some source fields can be skipped in case when there are deleted fields. // -Some destination fields can be skipped in case when there // are new empty fields without fixed/default value. QString sql = QString("INSERT INTO %1 (").arg(d->conn->escapeIdentifier(newTable->name())); //insert list of dest. fields bool first = true; QString sourceFields; foreach_list( Field::ListIterator, it, newTable->fieldsIterator() ) { Field * const f = it.current(); QString renamedFieldName( fieldMap[ f->name() ] ); QString sourceSQLString; if (!renamedFieldName.isEmpty()) { //this field should be renamed sourceSQLString = d->conn->escapeIdentifier(renamedFieldName); } else if (!f->defaultValue().isNull()) { //this field has a default value defined //! @todo support expressions (eg. TODAY()) as a default value //! @todo this field can be notNull or notEmpty - check whether the default is ok //! (or do this checking also in the Table Designer?) sourceSQLString = d->conn->driver()->valueToSQL( f->type(), f->defaultValue() ); } else if (f->isNotNull()) { //this field cannot be null sourceSQLString = d->conn->driver()->valueToSQL( f->type(), KexiDB::emptyValueForType( f->type() ) ); } else if (f->isNotEmpty()) { //this field cannot be empty - use any nonempty value..., e.g. " " for text or 0 for number sourceSQLString = d->conn->driver()->valueToSQL( f->type(), KexiDB::notEmptyValueForType( f->type() ) ); } //! @todo support unique, validatationRule, unsigned flags... //! @todo check for foreignKey values... if (!sourceSQLString.isEmpty()) { if (first) { first = false; } else { sql.append( ", " ); sourceFields.append( ", " ); } sql.append( d->conn->escapeIdentifier( f->name() ) ); sourceFields.append( sourceSQLString ); } } sql.append(QString(") SELECT ") + sourceFields + " FROM " + oldTable->name()); KexiDBDbg << " ** " << sql << endl; if (!d->conn->executeSQL( sql )) { setError(d->conn); //! @todo delete newTable... args.result = false; return 0; } const QString oldTableName = oldTable->name(); /* args.result = d->conn->dropTable( oldTable ); if (!args.result || ~args.result) { setError(d->conn); //! @todo delete newTable... return 0; } oldTable = 0;*/ // Replace the old table with the new one (oldTable will be destroyed) if (!d->conn->alterTableName(*newTable, oldTableName, true /*replace*/)) { setError(d->conn); //! @todo delete newTable... args.result = false; return 0; } oldTable = 0; } if (!recreateTable) { if ((MainSchemaAlteringRequired & args.requirements) && !fieldsWithChangedMainSchema.isEmpty()) { //update main schema (kexi__fields) for changed fields foreach_list(QDictIterator, it, fieldsWithChangedMainSchema) { Field *f = newTable->field( it.currentKey() ); if (f) { if (!d->conn->storeMainFieldSchema(f)) { setError(d->conn); //! @todo delete newTable... args.result = false; return 0; } } } } } args.result = true; return newTable; } /*TableSchema* AlterTableHandler::execute(const QString& tableName, tristate &result, bool simulate) { return executeInternal( tableName, result, simulate, 0 ); } tristate AlterTableHandler::simulateExecution(const QString& tableName, QString& debugString) { tristate result; (void)executeInternal( tableName, result, true//simulate , &debugString ); return result; } */