You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/kexi/kexidb/alter.cpp

1116 lines
36 KiB

/* This file is part of the KDE project
Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl>
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 <tqmap.h>
#include <kstaticdeleter.h>
#include <stdlib.h>
namespace KexiDB {
class AlterTableHandler::Private
{
public:
Private()
{}
~Private()
{}
ActionList actions;
TQGuardedPtr<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 TQString& fieldName, int uid)
: ActionBase()
, m_fieldUID(uid)
, m_fieldName(fieldName)
{
}
AlterTableHandler::FieldActionBase::FieldActionBase(bool)
: ActionBase(true)
, m_fieldUID(-1)
{
}
AlterTableHandler::FieldActionBase::~FieldActionBase()
{
}
//--------------------------------------------------------
static KStaticDeleter< TQMap<TQCString,int> > KexiDB_alteringTypeForProperty_deleter;
TQMap<TQCString,int> *KexiDB_alteringTypeForProperty = 0;
int AlterTableHandler::alteringTypeForProperty(const TQCString& propertyName)
{
if (!KexiDB_alteringTypeForProperty) {
KexiDB_alteringTypeForProperty_deleter.setObject( KexiDB_alteringTypeForProperty,
new TQMap<TQCString,int>() );
#define I(name, type) \
KexiDB_alteringTypeForProperty->insert(TQCString(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(TQCString(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 << TQString("AlterTableHandler::alteringTypeForProperty(): property \"%1\" not found!")
.arg(propertyName.data()) << endl;
}
return res;
}
//---
AlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction(
const TQString& fieldName, const TQString& propertyName, const TQVariant& 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() ) );
}
TQString AlterTableHandler::ChangeFieldPropertyAction::debugString(const DebugOptions& debugOptions)
{
TQString s = TQString("Set \"%1\" property for table field \"%2\" to \"%3\"")
.arg(m_propertyName).arg(fieldName()).arg(m_newValue.toString());
if (debugOptions.showUID)
s.append(TQString(" (UID=%1)").arg(m_fieldUID));
return s;
}
static AlterTableHandler::ActionDict* createActionDict(
AlterTableHandler::ActionDictDict &fieldActions, int forFieldUID )
{
AlterTableHandler::ActionDict* dict = new AlterTableHandler::ActionDict(1009, false);
dict->setAutoDelete(true);
fieldActions.insert( forFieldUID, dict );
return dict;
}
static void debugAction(AlterTableHandler::ActionBase *action, int nestingLevel,
bool simulate, const TQString& prependString = TQString(), TQString* debugTarget = 0)
{
TQString 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)
{
TQString 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 = "??";
TQString dbg = TQString("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
TQString 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,
TQMap<TQString, TQString>& 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 TQString& 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
}
TQString AlterTableHandler::RemoveFieldAction::debugString(const DebugOptions& debugOptions)
{
TQString s = TQString("Remove table field \"%1\"").arg(fieldName());
if (debugOptions.showUID)
s.append(TQString(" (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,
TQMap<TQString, TQString>& 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() : TQString());
}
void AlterTableHandler::InsertFieldAction::updateAlteringRequirements()
{
//! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ?
setAlteringRequirements( PhysicalAlteringRequired );
//! @todo
}
TQString AlterTableHandler::InsertFieldAction::debugString(const DebugOptions& debugOptions)
{
TQString s = TQString("Insert table field \"%1\" at position %2")
.arg(m_field->name()).arg(m_index);
if (debugOptions.showUID)
s.append(TQString(" (UID=%1)").arg(m_fieldUID));
if (debugOptions.showFieldDebug)
s.append(TQString(" (%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
TQMap<TQCString, TQVariant> 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(
TQString("** Property-set actions moved to field definition itself:\n")+field().debugString(), 0);
#endif
}
else {
#ifdef KEXI_DEBUG_GUI
KexiUtils::addAlterTableActionDebug(
TQString("** 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,
TQMap<TQString, TQString>& 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 TQString& 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
}
TQString AlterTableHandler::MoveFieldPositionAction::debugString(const DebugOptions& debugOptions)
{
TQString s = TQString("Move table field \"%1\" to position %2")
.arg(fieldName()).arg(m_index);
if (debugOptions.showUID)
s.append(TQString(" (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 TQString& 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;
TQDict<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
TQString dbg = TQString("** 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 = TQString("** 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, TQString("%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) {
TQString tempDestTableName;
while (true) {
tempDestTableName = TQString("%1_temp%2%3").arg(newTable->name()).arg(TQString::number(rand(), 16)).arg(TQString::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;
TQMap<TQString, TQString> 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.
TQString sql = TQString("INSERT INTO %1 (").arg(d->conn->escapeIdentifier(newTable->name()));
//insert list of dest. fields
bool first = true;
TQString sourceFields;
foreach_list( Field::ListIterator, it, newTable->fieldsIterator() ) {
Field * const f = it.current();
TQString renamedFieldName( fieldMap[ f->name() ] );
TQString 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(TQString(") 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 TQString 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(TQDictIterator<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 TQString& tableName, tristate &result, bool simulate)
{
return executeInternal( tableName, result, simulate, 0 );
}
tristate AlterTableHandler::simulateExecution(const TQString& tableName, TQString& debugString)
{
tristate result;
(void)executeInternal( tableName, result, true//simulate
, &debugString );
return result;
}
*/