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.
1116 lines
36 KiB
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 <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;
|
|
}
|
|
*/
|