diff options
Diffstat (limited to 'kexi/kexidb/alter.cpp')
-rw-r--r-- | kexi/kexidb/alter.cpp | 1115 |
1 files changed, 1115 insertions, 0 deletions
diff --git a/kexi/kexidb/alter.cpp b/kexi/kexidb/alter.cpp new file mode 100644 index 00000000..f894b299 --- /dev/null +++ b/kexi/kexidb/alter.cpp @@ -0,0 +1,1115 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 Jaroslaw Staniek <[email protected]> + + 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 <kexiutils/utils.h> + +#include <qmap.h> + +#include <kstaticdeleter.h> + +#include <stdlib.h> + +namespace KexiDB { +class AlterTableHandler::Private +{ + public: + Private() + {} + ~Private() + {} + ActionList actions; + QGuardedPtr<Connection> 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<ChangeFieldPropertyAction*>(this)) + return *dynamic_cast<ChangeFieldPropertyAction*>(this); + return nullChangeFieldPropertyAction; +} + +AlterTableHandler::RemoveFieldAction& AlterTableHandler::ActionBase::toRemoveFieldAction() +{ + if (dynamic_cast<RemoveFieldAction*>(this)) + return *dynamic_cast<RemoveFieldAction*>(this); + return nullRemoveFieldAction; +} + +AlterTableHandler::InsertFieldAction& AlterTableHandler::ActionBase::toInsertFieldAction() +{ + if (dynamic_cast<InsertFieldAction*>(this)) + return *dynamic_cast<InsertFieldAction*>(this); + return nullInsertFieldAction; +} + +AlterTableHandler::MoveFieldPositionAction& AlterTableHandler::ActionBase::toMoveFieldPositionAction() +{ + if (dynamic_cast<MoveFieldPositionAction*>(this)) + return *dynamic_cast<MoveFieldPositionAction*>(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<QCString,int> > KexiDB_alteringTypeForProperty_deleter; +QMap<QCString,int> *KexiDB_alteringTypeForProperty = 0; + +int AlterTableHandler::alteringTypeForProperty(const QCString& propertyName) +{ + if (!KexiDB_alteringTypeForProperty) { + KexiDB_alteringTypeForProperty_deleter.setObject( KexiDB_alteringTypeForProperty, + new QMap<QCString,int>() ); +#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: <field name="..."> + 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<AlterTableHandler::FieldActionBase*>(it.current())) //retrieve field name from the 1st related action + fieldName = dynamic_cast<AlterTableHandler::FieldActionBase*>(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<ChangeFieldPropertyAction*>(renameActionLikeThis)) { + // 1. instead of having rename(fieldName(), newValue()) action, + // let's have rename(fieldName(), otherName) action + dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue + = dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue; +/* AlterTableHandler::ChangeFieldPropertyAction* newRenameAction + = new AlterTableHandler::ChangeFieldPropertyAction( *this ); + newRenameAction->m_newValue = dynamic_cast<ChangeFieldPropertyAction*>(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<FieldActionBase*>(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<QString, QString>& 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 <TYPE> 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 <TYPE>"; + ( 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 <TYPE> 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<QString, QString>& 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<QCString, QVariant> values; + for (ActionDictIterator it(*actionsForThisField); it.current();) { + ChangeFieldPropertyAction* changePropertyAction = dynamic_cast<ChangeFieldPropertyAction*>(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<QString, QString>& 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<char> 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<ChangeFieldPropertyAction*>(it2.current())) { + // Remember, this will be used when recreateTable is false to update kexi__fields, below. + fieldsWithChangedMainSchema.insert( + dynamic_cast<ChangeFieldPropertyAction*>(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; i<allActionsCount; i++) { + debugAction(actionsVector[i], 1, args.simulate, QString("%1: ").arg(i+1), args.debugString); + } + + if (args.requirements == 0) {//nothing to do + args.result = true; + return oldTable; + } + if (args.simulate) {//do not execute + args.result = true; + return oldTable; + } +// @todo transaction! + + // Create new TableSchema + TableSchema *newTable = recreateTable ? new TableSchema(*oldTable, false/*!copy id*/) : oldTable; + // find nonexisting temp name for new table schema + if (recreateTable) { + QString tempDestTableName; + while (true) { + tempDestTableName = QString("%1_temp%2%3").arg(newTable->name()).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<QString, QString> 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<allActionsCount; i++) { + action = actionsVector[i]; + if (!action) + continue; + //remember the current Field object because soon we may be unable to find it by name: + FieldActionBase *fieldAction = dynamic_cast<FieldActionBase*>(action); + if (!fieldAction) { + currentField = 0; + } + else { + if (lastUID != fieldAction->uid()) { + currentField = newTable->field( fieldAction->fieldName() ); + lastUID = currentField ? fieldAction->uid() : -1; + } + InsertFieldAction *insertFieldAction = dynamic_cast<InsertFieldAction*>(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; i<allActionsCount; i++) { + action = actionsVector[i]; + if (!action) + continue; + args.result = action->execute(*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<char>, 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; +} +*/ |