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.
469 lines
18 KiB
469 lines
18 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.
|
|
*/
|
|
|
|
#ifndef KEXIDB_ALTER_H
|
|
#define KEXIDB_ALTER_H
|
|
|
|
#include "connection.h"
|
|
|
|
#include <qvaluelist.h>
|
|
#include <qasciidict.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
namespace KexiDB
|
|
{
|
|
class Connection;
|
|
class ConnectionData;
|
|
|
|
//! @short A tool for handling altering database table schema.
|
|
/*! In relational (and other) databases, table schema altering is not an easy task.
|
|
It may be considered as easy if there is no data that user wants to keep while
|
|
the table schema is altered. Otherwise, if the table is alredy filled with data,
|
|
there could be no easy algorithm like:
|
|
1. Drop existing table
|
|
2. Create new one with altered schema.
|
|
|
|
Instead, more complex algorithm is needed. To perform the table schema alteration,
|
|
a list of well defined atomic operations is used as a "recipe".
|
|
|
|
1. Look at the current data, and:
|
|
1.1. analyze what values will be removed (in case of impossible conversion
|
|
or table field removal);
|
|
1.2. analyze what values can be converted (e.g. from numeric types to text), and so on.
|
|
2. Optimize the atomic actions knowing that sometimes a compilation of one action
|
|
and another that's opposite to the first means "do nothing". The optimization
|
|
is a simulating of actions' execution.
|
|
For example, when both action A="change field name from 'city' to 'town'"
|
|
and action B="change field name from 'town' to 'city'" is specified, the compilation
|
|
of the actions means "change field name from 'city' to 'city'", what is a NULL action.
|
|
On the other hand, we need to execute all the actions on the destination table
|
|
in proper order, and not just drop them. For the mentioned example, between actions
|
|
A and B there can be an action like C="change the type of field 'city' to LongText".
|
|
If A and B were simply removed, C would become invalid (there is no 'city' field).
|
|
3. Ask user whether she agrees with the results of analysis mentioned in 1.
|
|
3.2. Additionally, it may be possible to get some hints from the user, as humans usually
|
|
know more about logic behind the altered table schema than any machine.
|
|
If the user provided hints about the altering, apply them to the actions list.
|
|
4. Create (empty) destination table schema with temporary name, using
|
|
the information collected so far.
|
|
5. Copy the data from the source to destionation table. Convert values,
|
|
move them between fields, using the information collected.
|
|
6. Remove the source table.
|
|
7. Rename the destination table to the name previously assigned for the source table.
|
|
|
|
Notes:
|
|
* The actions 4 to 7 should be performed within a database transaction.
|
|
* [todo] We want to take care about database relationships as well.
|
|
For example, is a table field is removed, relationships related to this field should
|
|
be also removed (similar rules as in the Query Designer).
|
|
* Especially, care about primary keys and uniquess (indices). Recreate them when needed.
|
|
The problem could be if such analysis may require to fetch the entire table data
|
|
to the client side. Use "SELECT INTO" statments if possible to avoid such a treat.
|
|
|
|
The AlterTableHandler is used in Kexi's Table Designer.
|
|
Already opened Connection object is needed.
|
|
|
|
Use case:
|
|
\code
|
|
Connection *conn = ...
|
|
|
|
// add some actions (in reality this is performed by tracking user's actions)
|
|
// Actions 1, 2 will require physical table altering PhysicalAltering
|
|
// Action 3 will only require changes in kexi__fields
|
|
// Action 4 will only require changes in extended table schema written in kexi__objectdata
|
|
AlterTable::ActionList list;
|
|
|
|
// 1. rename the "city" field to "town"
|
|
list << new ChangeFieldPropertyAction("city", "name", "town")
|
|
|
|
// 2. change type of "town" field to "LongText"
|
|
<< new ChangeFieldPropertyAction("town", "type", "LongText")
|
|
|
|
// 3. set caption of "town" field to "Town"
|
|
<< new ChangeFieldPropertyAction("town", "caption", "Town")
|
|
|
|
// 4. set visible decimal places to 4 for "cost" field
|
|
<< new ChangeFieldPropertyAction("cost", "visibleDecimalPlaces", 4)
|
|
|
|
AlterTableHandler::execute( *conn );
|
|
|
|
\endcode
|
|
|
|
Actions for Alter
|
|
*/
|
|
class KEXI_DB_EXPORT AlterTableHandler : public Object
|
|
{
|
|
public:
|
|
class ChangeFieldPropertyAction;
|
|
class RemoveFieldAction;
|
|
class InsertFieldAction;
|
|
class MoveFieldPositionAction;
|
|
|
|
//! Defines flags for possible altering requirements; can be combined.
|
|
enum AlteringRequirements {
|
|
/*! Physical table altering is required; e.g. ALTER TABLE ADD COLUMN. */
|
|
PhysicalAlteringRequired = 1,
|
|
|
|
/*! Data conversion is required; e.g. converting integer
|
|
values to string after changing column type from integer to text. */
|
|
DataConversionRequired = 2,
|
|
|
|
/* Changes to the main table schema (in kexi__fields) required,
|
|
this does not require physical changes for the table;
|
|
e.g. changing value of the "caption" or "description" property. */
|
|
MainSchemaAlteringRequired = 4,
|
|
|
|
/* Only changes to extended table schema required,
|
|
this does not require physical changes for the table;
|
|
e.g. changing value of the "visibleDecimalPlaces" property
|
|
or any of the custom properties. */
|
|
ExtendedSchemaAlteringRequired = 8,
|
|
|
|
/*! Convenience flag, changes to the main or extended schema is required. */
|
|
SchemaAlteringRequired = ExtendedSchemaAlteringRequired | MainSchemaAlteringRequired
|
|
};
|
|
|
|
class ActionBase;
|
|
typedef QAsciiDict<ActionBase> ActionDict; //!< for collecting actions related to a single field
|
|
typedef QIntDict<ActionDict> ActionDictDict; //!< for collecting groups of actions by field UID
|
|
typedef QAsciiDictIterator<ActionBase> ActionDictIterator;
|
|
typedef QIntDictIterator<ActionDict> ActionDictDictIterator;
|
|
typedef QPtrVector<ActionBase> ActionVector; //!< for collecting actions related to a single field
|
|
|
|
//! Defines a type for action list.
|
|
typedef QPtrList<ActionBase> ActionList;
|
|
|
|
//! Defines a type for action list's iterator.
|
|
typedef QPtrListIterator<ActionBase> ActionListIterator;
|
|
|
|
//! Abstract base class used for implementing all the AlterTable actions.
|
|
class KEXI_DB_EXPORT ActionBase {
|
|
public:
|
|
ActionBase(bool null = false);
|
|
virtual ~ActionBase();
|
|
|
|
ChangeFieldPropertyAction& toChangeFieldPropertyAction();
|
|
RemoveFieldAction& toRemoveFieldAction();
|
|
InsertFieldAction& toInsertFieldAction();
|
|
MoveFieldPositionAction& toMoveFieldPositionAction();
|
|
|
|
//! \return true if the action is NULL; used in the Table Designer
|
|
//! for temporarily collecting actions that have no effect at all.
|
|
bool isNull() const { return m_null; }
|
|
|
|
//! Controls debug options for actions. Used in debugString() and debug().
|
|
class DebugOptions
|
|
{
|
|
public:
|
|
DebugOptions() : showUID(true), showFieldDebug(false) {}
|
|
|
|
//! true if UID should be added to the action debug string (the default)
|
|
bool showUID : 1;
|
|
|
|
//! true if the field associated with the action (if exists) should
|
|
//! be appended to the debug string (default is false)
|
|
bool showFieldDebug : 1;
|
|
};
|
|
|
|
virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()) {
|
|
Q_UNUSED(debugOptions); return "ActionBase"; }
|
|
void debug(const DebugOptions& debugOptions = DebugOptions()) {
|
|
KexiDBDbg << debugString(debugOptions)
|
|
<< " (req = " << alteringRequirements() << ")" << endl; }
|
|
|
|
protected:
|
|
//! Sets requirements for altering; used internally by AlterTableHandler object
|
|
void setAlteringRequirements( int alteringRequirements )
|
|
{ m_alteringRequirements = alteringRequirements; }
|
|
|
|
int alteringRequirements() const { return m_alteringRequirements; }
|
|
|
|
virtual void updateAlteringRequirements() {};
|
|
|
|
/*! Simplifies \a fieldActions dictionary. If this action has to be inserted
|
|
Into the dictionary, an ActionDict is created first and then a copy of this action
|
|
is inserted into it. */
|
|
virtual void simplifyActions(ActionDictDict &fieldActions) { Q_UNUSED(fieldActions); }
|
|
|
|
/*! After calling simplifyActions() for each action,
|
|
shouldBeRemoved() is called for them as an additional step.
|
|
This is used for ChangeFieldPropertyAction items so actions
|
|
that do not change property values are removed. */
|
|
virtual bool shouldBeRemoved(ActionDictDict &fieldActions) {
|
|
Q_UNUSED(fieldActions); return false; }
|
|
|
|
virtual tristate updateTableSchema(TableSchema &table, Field* field,
|
|
QMap<QString, QString>& fieldMap)
|
|
{ Q_UNUSED(table); Q_UNUSED(field); Q_UNUSED(fieldMap); return true; }
|
|
|
|
private:
|
|
//! Performs physical execution of this action.
|
|
virtual tristate execute(Connection& /*conn*/, TableSchema& /*table*/) { return true; }
|
|
|
|
//! requirements for altering; used internally by AlterTableHandler object
|
|
int m_alteringRequirements;
|
|
|
|
//! @internal used for "simplify" algorithm
|
|
int m_order;
|
|
|
|
bool m_null : 1;
|
|
|
|
friend class AlterTableHandler;
|
|
};
|
|
|
|
//! Abstract base class used for implementing table field-related actions.
|
|
class KEXI_DB_EXPORT FieldActionBase : public ActionBase {
|
|
public:
|
|
FieldActionBase(const QString& fieldName, int uid);
|
|
FieldActionBase(bool);
|
|
virtual ~FieldActionBase();
|
|
|
|
//! \return field name for this action
|
|
QString fieldName() const { return m_fieldName; }
|
|
|
|
/*! \return field's unique identifier
|
|
This id is needed because in the meantime there can be more than one
|
|
field sharing the same name, so we need to identify them unambiguously.
|
|
After the (valid) altering is completed all the names will be unique.
|
|
|
|
Example scenario when user exchanged the field names:
|
|
1. At the beginning: [field A], [field B]
|
|
2. Rename the 1st field to B: [field B], [field B]
|
|
3. Rename the 2nd field to A: [field B], [field A] */
|
|
int uid() const { return m_fieldUID; }
|
|
|
|
//! Sets field name for this action
|
|
void setFieldName(const QString& fieldName) { m_fieldName = fieldName; }
|
|
|
|
protected:
|
|
|
|
//! field's unique identifier, @see uid()
|
|
int m_fieldUID;
|
|
private:
|
|
QString m_fieldName;
|
|
};
|
|
|
|
/*! Defines an action for changing a single property value of a table field.
|
|
Supported properties are currently:
|
|
"name", "type", "caption", "description", "unsigned", "length", "precision",
|
|
"width", "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty",
|
|
"autoIncrement", "indexed", "visibleDecimalPlaces"
|
|
|
|
More to come.
|
|
*/
|
|
class KEXI_DB_EXPORT ChangeFieldPropertyAction : public FieldActionBase {
|
|
public:
|
|
ChangeFieldPropertyAction(const QString& fieldName,
|
|
const QString& propertyName, const QVariant& newValue, int uid);
|
|
//! @internal, used for constructing null action
|
|
ChangeFieldPropertyAction(bool null);
|
|
virtual ~ChangeFieldPropertyAction();
|
|
|
|
QString propertyName() const { return m_propertyName; }
|
|
QVariant newValue() const { return m_newValue; }
|
|
virtual QString debugString(const DebugOptions& debugOptions = DebugOptions());
|
|
|
|
virtual void simplifyActions(ActionDictDict &fieldActions);
|
|
|
|
virtual bool shouldBeRemoved(ActionDictDict &fieldActions);
|
|
|
|
virtual tristate updateTableSchema(TableSchema &table, Field* field,
|
|
QMap<QString, QString>& fieldMap);
|
|
|
|
protected:
|
|
virtual void updateAlteringRequirements();
|
|
|
|
//! Performs physical execution of this action.
|
|
virtual tristate execute(Connection &conn, TableSchema &table);
|
|
|
|
QString m_propertyName;
|
|
QVariant m_newValue;
|
|
};
|
|
|
|
//! Defines an action for removing a single table field.
|
|
class KEXI_DB_EXPORT RemoveFieldAction : public FieldActionBase {
|
|
public:
|
|
RemoveFieldAction(const QString& fieldName, int uid);
|
|
RemoveFieldAction(bool);
|
|
virtual ~RemoveFieldAction();
|
|
|
|
virtual QString debugString(const DebugOptions& debugOptions = DebugOptions());
|
|
|
|
virtual void simplifyActions(ActionDictDict &fieldActions);
|
|
|
|
virtual tristate updateTableSchema(TableSchema &table, Field* field,
|
|
QMap<QString, QString>& fieldMap);
|
|
|
|
protected:
|
|
virtual void updateAlteringRequirements();
|
|
|
|
//! Performs physical execution of this action.
|
|
virtual tristate execute(Connection &conn, TableSchema &table);
|
|
};
|
|
|
|
//! Defines an action for inserting a single table field.
|
|
class KEXI_DB_EXPORT InsertFieldAction : public FieldActionBase {
|
|
public:
|
|
InsertFieldAction(int fieldIndex, KexiDB::Field *newField, int uid);
|
|
//copy ctor
|
|
InsertFieldAction(const InsertFieldAction& action);
|
|
InsertFieldAction(bool);
|
|
virtual ~InsertFieldAction();
|
|
|
|
int index() const { return m_index; }
|
|
void setIndex( int index ) { m_index = index; }
|
|
KexiDB::Field& field() const { return *m_field; }
|
|
void setField(KexiDB::Field* field);
|
|
virtual QString debugString(const DebugOptions& debugOptions = DebugOptions());
|
|
|
|
virtual void simplifyActions(ActionDictDict &fieldActions);
|
|
|
|
virtual tristate updateTableSchema(TableSchema &table, Field* field,
|
|
QMap<QString, QString>& fieldMap);
|
|
|
|
protected:
|
|
virtual void updateAlteringRequirements();
|
|
|
|
//! Performs physical execution of this action.
|
|
virtual tristate execute(Connection &conn, TableSchema &table);
|
|
|
|
int m_index;
|
|
|
|
private:
|
|
KexiDB::Field *m_field;
|
|
};
|
|
|
|
/*! Defines an action for moving a single table field to a different
|
|
position within table schema. */
|
|
class KEXI_DB_EXPORT MoveFieldPositionAction : public FieldActionBase {
|
|
public:
|
|
MoveFieldPositionAction(int fieldIndex, const QString& fieldName, int uid);
|
|
MoveFieldPositionAction(bool);
|
|
virtual ~MoveFieldPositionAction();
|
|
|
|
int index() const { return m_index; }
|
|
virtual QString debugString(const DebugOptions& debugOptions = DebugOptions());
|
|
|
|
virtual void simplifyActions(ActionDictDict &fieldActions);
|
|
|
|
protected:
|
|
virtual void updateAlteringRequirements();
|
|
|
|
//! Performs physical execution of this action.
|
|
virtual tristate execute(Connection &conn, TableSchema &table);
|
|
|
|
int m_index;
|
|
};
|
|
|
|
AlterTableHandler(Connection &conn);
|
|
|
|
virtual ~AlterTableHandler();
|
|
|
|
/*! Appends \a action for the alter table tool. */
|
|
void addAction(ActionBase* action);
|
|
|
|
/*! Provided for convenience, @see addAction(const ActionBase& action). */
|
|
AlterTableHandler& operator<< ( ActionBase* action );
|
|
|
|
/*! Removes an action from the alter table tool at index \a index. */
|
|
void removeAction(int index);
|
|
|
|
/*! Removes all actions from the alter table tool. */
|
|
void clear();
|
|
|
|
/*! Sets \a actions for the alter table tool. Previous actions are cleared.
|
|
\a actions will be owned by the AlterTableHandler object. */
|
|
void setActions(const ActionList& actions);
|
|
|
|
/*! \return a list of actions for this AlterTable object.
|
|
Use ActionBase::ListIterator to iterate over the list items. */
|
|
const ActionList& actions() const;
|
|
|
|
//! Arguments for AlterTableHandler::execute().
|
|
class ExecutionArguments {
|
|
public:
|
|
ExecutionArguments()
|
|
: debugString(0)
|
|
, requirements(0)
|
|
, result(false)
|
|
, simulate(false)
|
|
, onlyComputeRequirements(false)
|
|
{
|
|
}
|
|
/*! If not 0, debug is directed here. Used only in the alter table test suite. */
|
|
QString* debugString;
|
|
/*! Requrements computed, a combination of AlteringRequirements values. */
|
|
int requirements;
|
|
/*! Set to true on success, to false on failure. */
|
|
tristate result;
|
|
/*! Used only in the alter table test suite. */
|
|
bool simulate : 1;
|
|
/*! Set to true if requirements should be computed
|
|
and the execute() method should return afterwards. */
|
|
bool onlyComputeRequirements;
|
|
};
|
|
|
|
/*! Performs table alteration using predefined actions for table named \a tableName,
|
|
assuming it already exists. The Connection object passed to the constructor must exist,
|
|
must be connected and a database must be used. The connection must not be read-only.
|
|
|
|
If args.simulate is true, the execution is only simulated, i.e. al lactions are processed
|
|
like for regular execution but no changes are performed physically.
|
|
This mode is used only for debugging purposes.
|
|
|
|
@todo For some cases, table schema can completely change, so it will be needed
|
|
to refresh all objects depending on it.
|
|
Implement this!
|
|
|
|
Sets args.result to true on success, to false on failure or when the above requirements are not met
|
|
(then, you can get a detailed error message from KexiDB::Object).
|
|
When the action has been cancelled (stopped), args.result is set to cancelled value.
|
|
If args.debugString is not 0, it will be filled with debugging output.
|
|
\return the new table schema object created as a result of schema altering.
|
|
The old table is returned if recreating table schema was not necessary or args.simulate is true.
|
|
0 is returned if args.result is not true. */
|
|
TableSchema* execute(const QString& tableName, ExecutionArguments & args);
|
|
|
|
//! Displays debug information about all actions collected by the handler.
|
|
void debug();
|
|
|
|
/*! Like execute() with simulate set to true, but debug is directed to debugString.
|
|
This function is used only in the alter table test suite. */
|
|
// tristate simulateExecution(const QString& tableName, QString& debugString);
|
|
|
|
/*! Helper. \return a combination of AlteringRequirements values decribing altering type required
|
|
when a given property field's \a propertyName is altered.
|
|
Used internally AlterTableHandler. Moreover it can be also used in the Table Designer's code
|
|
as a temporary replacement before AlterTableHandler is fully implemented.
|
|
Thus, it is possible to identify properties that have no PhysicalAlteringRequired flag set
|
|
(e.g. caption or extended properties like visibleDecimalPlaces. */
|
|
static int alteringTypeForProperty(const QCString& propertyName);
|
|
|
|
protected:
|
|
// TableSchema* executeInternal(const QString& tableName, tristate& result, bool simulate = false,
|
|
// QString* debugString = 0);
|
|
|
|
class Private;
|
|
Private *d;
|
|
};
|
|
}
|
|
|
|
#endif
|