summaryrefslogtreecommitdiffstats
path: root/kexi/kexidb/alter.h
diff options
context:
space:
mode:
Diffstat (limited to 'kexi/kexidb/alter.h')
-rw-r--r--kexi/kexidb/alter.h468
1 files changed, 468 insertions, 0 deletions
diff --git a/kexi/kexidb/alter.h b/kexi/kexidb/alter.h
new file mode 100644
index 00000000..1e6d8e87
--- /dev/null
+++ b/kexi/kexidb/alter.h
@@ -0,0 +1,468 @@
+/* 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