File indexing completed on 2024-04-21 15:30:09

0001 /* This file is part of the KDE project
0002    Copyright (C) 2006-2012 Jarosław Staniek <staniek@kde.org>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #ifndef KDB_ALTER_H
0021 #define KDB_ALTER_H
0022 
0023 #include "KDbUtils.h"
0024 #include "KDbResult.h"
0025 #include "KDbTristate.h"
0026 #include "KDbTableSchema.h"
0027 
0028 #include <QList>
0029 #include <QHash>
0030 
0031 class KDbConnection;
0032 
0033 //! @short A tool for handling altering database table schema.
0034 /*! In relational (and other) databases, table schema altering is not an easy task.
0035  It may be considered as easy if there is no data that user wants to keep while
0036  the table schema is altered. Otherwise, if the table is alredy filled with data,
0037  there could be no easy algorithm like:
0038  1. Drop existing table
0039  2. Create new one with altered schema.
0040 
0041  Instead, more complex algorithm is needed. To perform the table schema alteration,
0042  a list of well defined atomic operations is used as a "recipe".
0043 
0044  1. Look at the current data, and:
0045  1.1. analyze what values will be removed (in case of impossible conversion
0046       or table field removal);
0047  1.2. analyze what values can be converted (e.g. from numeric types to text), and so on.
0048  2. Optimize the atomic actions knowing that sometimes a compilation of one action
0049     and another that's opposite to the first means "do nothing". The optimization
0050     is a simulating of actions' execution.
0051     For example, when both action A="change field name from 'city' to 'town'"
0052     and action B="change field name from 'town' to 'city'" is specified, the compilation
0053     of the actions means "change field name from 'city' to 'city'", what is a NULL action.
0054     On the other hand, we need to execute all the actions on the destination table
0055     in proper order, and not just drop them. For the mentioned example, between actions
0056     A and B there can be an action like C="change the type of field 'city' to LongText".
0057     If A and B were simply removed, C would become invalid (there is no 'city' field).
0058  3. Ask user whether she agrees with the results of analysis mentioned in 1.
0059  3.2. Additionally, it may be possible to get some hints from the user, as humans usually
0060       know more about logic behind the altered table schema than any machine.
0061       If the user provided hints about the altering, apply them to the actions list.
0062  4. Create (empty) destination table schema with temporary name, using
0063     the information collected so far.
0064  5. Copy the data from the source to destionation table. Convert values,
0065     move them between fields, using the information collected.
0066  6. Remove the source table.
0067  7. Rename the destination table to the name previously assigned for the source table.
0068 
0069  Notes:
0070  * The actions 4 to 7 should be performed within a database transaction.
0071  * [todo] We want to take care about database relationships as well.
0072     For example, is a table field is removed, relationships related to this field should
0073     be also removed (similar rules as in the Query Designer).
0074  * Especially, care about primary keys and uniquess (indices). Recreate them when needed.
0075    The problem could be if such analysis may require to fetch the entire table data
0076    to the client side. Use "SELECT INTO" statements if possible to avoid such a treat.
0077 
0078  The KDbAlterTableHandler is used in Kexi's Table Designer.
0079  Already opened KDbConnection object is needed.
0080 
0081  Use case:
0082  @code
0083   KDbConnection *conn = ...
0084 
0085   // add some actions (in reality this is performed by tracking user's actions)
0086   // Actions 1, 2 will require physical table altering PhysicalAltering
0087   // Action 3 will only require changes in kexi__fields
0088   // Action 4 will only require changes in extended table schema written in kexi__objectdata
0089   AlterTable::ActionList list;
0090 
0091   // 1. rename the "city" field to "town"
0092   list << new ChangeFieldPropertyAction("city", "name", "town")
0093 
0094   // 2. change type of "town" field to "LongText"
0095     << new ChangeFieldPropertyAction("town", "type", "LongText")
0096 
0097   // 3. set caption of "town" field to "Town"
0098     << new ChangeFieldPropertyAction("town", "caption", "Town")
0099 
0100   // 4. set visible decimal places to 4 for "cost" field
0101     << new ChangeFieldPropertyAction("cost", "visibleDecimalPlaces", 4)
0102 
0103   KDbAlterTableHandler::execute( *conn );
0104 
0105  @endcode
0106 
0107  Actions for Alter
0108 */
0109 class KDB_EXPORT KDbAlterTableHandler : public KDbResultable
0110 {
0111 public:
0112     class ChangeFieldPropertyAction;
0113     class RemoveFieldAction;
0114     class InsertFieldAction;
0115     class MoveFieldPositionAction;
0116 
0117     //! Defines flags for possible altering requirements; can be combined.
0118     enum AlteringRequirements {
0119         /*! Physical table altering is required; e.g. ALTER TABLE ADD COLUMN. */
0120         PhysicalAlteringRequired = 1,
0121 
0122         /*! Data conversion is required; e.g. converting integer
0123          values to string after changing column type from integer to text. */
0124         DataConversionRequired = 2,
0125 
0126         /*! Changes to the main table schema (in kexi__fields) required,
0127          this does not require physical changes for the table;
0128          e.g. changing value of the "caption" or "description" property. */
0129         MainSchemaAlteringRequired = 4,
0130 
0131         /*! Only changes to extended table schema required,
0132          this does not require physical changes for the table;
0133          e.g. changing value of the "visibleDecimalPlaces" property
0134          or any of the custom properties. */
0135         ExtendedSchemaAlteringRequired = 8,
0136 
0137         /*! Convenience flag, changes to the main or extended schema is required. */
0138         SchemaAlteringRequired = ExtendedSchemaAlteringRequired | MainSchemaAlteringRequired
0139     };
0140 
0141     class ActionBase;
0142     //! For collecting actions related to a single field
0143     typedef KDbUtils::AutodeletedHash<QByteArray, ActionBase*> ActionDict;
0144     typedef KDbUtils::AutodeletedHash<int, ActionDict*> ActionDictDict; //!< for collecting groups of actions by field UID
0145     typedef QHash<QByteArray, ActionBase*>::Iterator ActionDictIterator;
0146     typedef QHash<QByteArray, ActionBase*>::ConstIterator ActionDictConstIterator;
0147     typedef QHash<int, ActionDict*>::Iterator ActionDictDictIterator;
0148     typedef QHash<int, ActionDict*>::ConstIterator ActionDictDictConstIterator;
0149     typedef QVector<ActionBase*> ActionsVector; //!< for collecting actions related to a single field
0150 
0151     //! Defines a type for action list.
0152     typedef QList<ActionBase*> ActionList;
0153 
0154     //! Defines a type for action list's iterator.
0155     typedef QList<ActionBase*>::ConstIterator ActionListIterator;
0156 
0157     //! Abstract base class used for implementing all the AlterTable actions.
0158     class KDB_EXPORT ActionBase
0159     {
0160     public:
0161         virtual ~ActionBase();
0162 
0163         ChangeFieldPropertyAction& toChangeFieldPropertyAction();
0164         RemoveFieldAction& toRemoveFieldAction();
0165         InsertFieldAction& toInsertFieldAction();
0166         MoveFieldPositionAction& toMoveFieldPositionAction();
0167 
0168         //! @return true if the action is NULL; used in the Table Designer
0169         //! for temporarily collecting actions that have no effect at all.
0170         inline bool isNull() const {
0171             return m_null;
0172         }
0173 
0174         //! Controls debug options for actions. Used in debugString() and debug().
0175         class DebugOptions
0176         {
0177         public:
0178             inline DebugOptions() : showUID(true), showFieldDebug(false) {}
0179 
0180             //! true if UID should be added to the action debug string (the default)
0181             bool showUID;
0182 
0183             //! true if the field associated with the action (if exists) should
0184             //! be appended to the debug string (default is false)
0185             bool showFieldDebug;
0186         };
0187 
0188         inline virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()) {
0189             Q_UNUSED(debugOptions); return QLatin1String("ActionBase");
0190         }
0191 
0192 //! @todo add QDebug operator <<
0193         void debug(const DebugOptions& debugOptions = DebugOptions());
0194 
0195     protected:
0196         //! @internal, used for constructing null action
0197         explicit ActionBase(bool null);
0198 
0199         //! Sets requirements for altering; used internally by KDbAlterTableHandler object
0200         inline void setAlteringRequirements(int alteringRequirements) {
0201             m_alteringRequirements = alteringRequirements;
0202         }
0203 
0204         inline int alteringRequirements() const {
0205             return m_alteringRequirements;
0206         }
0207 
0208         inline virtual void updateAlteringRequirements() {}
0209 
0210         /*! Simplifies @a fieldActions dictionary. If this action has to be inserted
0211          Into the dictionary, an ActionDict is created first and then a copy of this action
0212          is inserted into it. */
0213         inline virtual void simplifyActions(ActionDictDict *fieldActions) {
0214             Q_UNUSED(fieldActions);
0215         }
0216 
0217         /*! After calling simplifyActions() for each action,
0218          shouldBeRemoved() is called for them as an additional step.
0219          This is used for ChangeFieldPropertyAction items so actions
0220          that do not change property values are removed. */
0221         inline virtual bool shouldBeRemoved(ActionDictDict *fieldActions) {
0222             Q_UNUSED(fieldActions); return false;
0223         }
0224 
0225         inline virtual tristate updateTableSchema(KDbTableSchema* table, KDbField* field,
0226                                            QHash<QString, QString>* fieldHash) {
0227             Q_UNUSED(table); Q_UNUSED(field); Q_UNUSED(fieldHash); return true;
0228         }
0229 
0230     private:
0231         //! Performs physical execution of this action.
0232         inline virtual tristate execute(KDbConnection* /*conn*/, KDbTableSchema* /*table*/) {
0233             return true;
0234         }
0235 
0236         //! requirements for altering; used internally by KDbAlterTableHandler object
0237         int m_alteringRequirements;
0238 
0239         //! @internal used for "simplify" algorithm
0240         int m_order;
0241 
0242         const bool m_null;
0243 
0244         friend class KDbAlterTableHandler;
0245     };
0246 
0247     //! Abstract base class used for implementing table field-related actions.
0248     class KDB_EXPORT FieldActionBase : public ActionBase
0249     {
0250     public:
0251         FieldActionBase(const QString& fieldName, int uid);
0252         ~FieldActionBase() override;
0253 
0254         //! @return field name for this action
0255         inline QString fieldName() const {
0256             return m_fieldName;
0257         }
0258 
0259         /*! @return field's unique identifier
0260          This id is needed because in the meantime there can be more than one
0261          field sharing the same name, so we need to identify them unambiguously.
0262          After the (valid) altering is completed all the names will be unique.
0263 
0264          Example scenario when user exchanged the field names:
0265          1. At the beginning: [field A], [field B]
0266          2. Rename the 1st field to B: [field B], [field B]
0267          3. Rename the 2nd field to A: [field B], [field A] */
0268         inline int uid() const {
0269             return m_fieldUID;
0270         }
0271 
0272         //! Sets field name for this action
0273         inline void setFieldName(const QString& fieldName) {
0274             m_fieldName = fieldName;
0275         }
0276 
0277     protected:
0278         //! @internal, used for constructing null action
0279         explicit FieldActionBase(bool null);
0280 
0281         //! field's unique identifier, @see uid()
0282         int m_fieldUID;
0283     private:
0284         QString m_fieldName;
0285     };
0286 
0287     /*! Defines an action for changing a single property value of a table field.
0288      Supported properties are currently:
0289      "name", "type", "caption", "description", "unsigned", "maxLength", "precision",
0290      "defaultWidth", "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty",
0291      "autoIncrement", "indexed", "visibleDecimalPlaces"
0292 
0293      More to come.
0294     */
0295     class KDB_EXPORT ChangeFieldPropertyAction : public FieldActionBase
0296     {
0297     public:
0298         ChangeFieldPropertyAction(const QString& fieldName,
0299                                   const QString& propertyName, const QVariant& newValue, int uid);
0300 
0301         //! Creates null action
0302         ChangeFieldPropertyAction();
0303 
0304         ~ChangeFieldPropertyAction() override;
0305 
0306         inline QString propertyName() const {
0307             return m_propertyName;
0308         }
0309         inline QVariant newValue() const {
0310             return m_newValue;
0311         }
0312         QString debugString(const DebugOptions& debugOptions = DebugOptions()) override;
0313 
0314         void simplifyActions(ActionDictDict *fieldActions) override;
0315 
0316         bool shouldBeRemoved(ActionDictDict *fieldActions) override;
0317 
0318         tristate updateTableSchema(KDbTableSchema *table, KDbField *field,
0319                                    QHash<QString, QString> *fieldHash) override;
0320 
0321     protected:
0322         //! @internal, used for constructing null action
0323         explicit ChangeFieldPropertyAction(bool null);
0324 
0325         void updateAlteringRequirements() override;
0326 
0327         //! Performs physical execution of this action.
0328         tristate execute(KDbConnection* conn, KDbTableSchema* table) override;
0329 
0330         QString m_propertyName;
0331         QVariant m_newValue;
0332     };
0333 
0334     //! Defines an action for removing a single table field.
0335     class KDB_EXPORT RemoveFieldAction : public FieldActionBase
0336     {
0337     public:
0338         RemoveFieldAction(const QString& fieldName, int uid);
0339 
0340         ~RemoveFieldAction() override;
0341 
0342         QString debugString(const DebugOptions& debugOptions = DebugOptions()) override;
0343 
0344         void simplifyActions(ActionDictDict *fieldActions) override;
0345 
0346         tristate updateTableSchema(KDbTableSchema *table, KDbField *field,
0347                                    QHash<QString, QString> *fieldHash) override;
0348 
0349     protected:
0350         //! @internal, used for constructing null action
0351         explicit RemoveFieldAction(bool null);
0352 
0353         void updateAlteringRequirements() override;
0354 
0355         //! Performs physical execution of this action.
0356         tristate execute(KDbConnection* conn, KDbTableSchema* table) override;
0357     };
0358 
0359     //! Defines an action for inserting a single table field.
0360     class KDB_EXPORT InsertFieldAction : public FieldActionBase
0361     {
0362     public:
0363         InsertFieldAction(int fieldIndex, KDbField *newField, int uid);
0364 
0365         //! copy ctor
0366         InsertFieldAction(const InsertFieldAction& action);
0367 
0368         //! Creates null action
0369         InsertFieldAction();
0370 
0371         ~InsertFieldAction() override;
0372 
0373         inline int index() const {
0374             return m_index;
0375         }
0376         inline void setIndex(int index) {
0377             m_index = index;
0378         }
0379         inline const KDbField* field() const {
0380             return m_field;
0381         }
0382         void setField(KDbField* field);
0383         QString debugString(const DebugOptions& debugOptions = DebugOptions()) override;
0384 
0385         void simplifyActions(ActionDictDict *fieldActions) override;
0386 
0387         tristate updateTableSchema(KDbTableSchema *table, KDbField *field,
0388                                    QHash<QString, QString> *fieldHash) override;
0389 
0390     protected:
0391         //! @internal, used for constructing null action
0392         explicit InsertFieldAction(bool null);
0393 
0394         void updateAlteringRequirements() override;
0395 
0396         //! Performs physical execution of this action.
0397         tristate execute(KDbConnection* conn, KDbTableSchema* table) override;
0398 
0399         int m_index;
0400 
0401     private:
0402         KDbField *m_field;
0403     };
0404 
0405     /*! Defines an action for moving a single table field to a different
0406      position within table schema. */
0407     class KDB_EXPORT MoveFieldPositionAction : public FieldActionBase
0408     {
0409     public:
0410         MoveFieldPositionAction(int fieldIndex, const QString& fieldName, int uid);
0411 
0412         ~MoveFieldPositionAction() override;
0413 
0414         inline int index() const {
0415             return m_index;
0416         }
0417         QString debugString(const DebugOptions& debugOptions = DebugOptions()) override;
0418 
0419         void simplifyActions(ActionDictDict *fieldActions) override;
0420 
0421     protected:
0422         //! @internal, used for constructing null action
0423         explicit MoveFieldPositionAction(bool null);
0424 
0425         void updateAlteringRequirements() override;
0426 
0427         //! Performs physical execution of this action.
0428         tristate execute(KDbConnection* conn, KDbTableSchema* table) override;
0429 
0430         int m_index;
0431     };
0432 
0433     explicit KDbAlterTableHandler(KDbConnection* conn);
0434 
0435     ~KDbAlterTableHandler() override;
0436 
0437     /*! Appends @a action for the alter table tool. */
0438     void addAction(ActionBase* action);
0439 
0440     /*! Provided for convenience, @see addAction(const ActionBase& action). */
0441     KDbAlterTableHandler& operator<< (ActionBase* action);
0442 
0443     /*! Removes an action from the alter table tool at index @a index. */
0444     void removeAction(int index);
0445 
0446     /*! Removes all actions from the alter table tool. */
0447     void clear();
0448 
0449     /*! Sets @a actions for the alter table tool. Previous actions are cleared.
0450      @a actions will be owned by the KDbAlterTableHandler object. */
0451     void setActions(const ActionList& actions);
0452 
0453     /*! @return a list of actions for this AlterTable object.
0454      Use ActionBase::ListIterator to iterate over the list items. */
0455     const ActionList& actions() const;
0456 
0457     //! Arguments for KDbAlterTableHandler::execute().
0458     class ExecutionArguments
0459     {
0460     public:
0461         inline ExecutionArguments()
0462                 : debugString(nullptr)
0463                 , requirements(0)
0464                 , result(false)
0465                 , simulate(false)
0466                 , onlyComputeRequirements(false) {
0467         }
0468         /*! If not 0, debug is directed here. Used only in the alter table test suite. */
0469         QString* debugString;
0470         /*! Requrements computed, a combination of AlteringRequirements values. */
0471         int requirements;
0472         /*! Set to true on success, to false on failure. */
0473         tristate result;
0474         /*! Used only in the alter table test suite. */
0475         bool simulate;
0476         /*! Set to true if requirements should be computed
0477          and the execute() method should return afterwards. */
0478         bool onlyComputeRequirements;
0479     private:
0480         Q_DISABLE_COPY(ExecutionArguments)
0481     };
0482 
0483     /*! Performs table alteration using predefined actions for table named @a tableName,
0484      assuming it already exists. The KDbConnection object passed to the constructor must exist,
0485      must be connected and a database must be used. The connection must not be read-only.
0486 
0487      If args.simulate is true, the execution is only simulated, i.e. al lactions are processed
0488      like for regular execution but no changes are performed physically.
0489      This mode is used only for debugging purposes.
0490 
0491     @todo For some cases, table schema can completely change, so it will be needed
0492      to refresh all objects depending on it.
0493      Implement this!
0494 
0495      Sets args.result to true on success, to false on failure or when the above requirements are not met
0496      (then, you can get a detailed error message from KDbObject).
0497      When the action has been cancelled (stopped), args.result is set to cancelled value.
0498      If args.debugString is not 0, it will be filled with debugging output.
0499      @return the new table schema object created as a result of schema altering.
0500      The old table is returned if recreating table schema was not necessary or args.simulate is true.
0501      0 is returned if args.result is not true. */
0502     KDbTableSchema* execute(const QString& tableName, ExecutionArguments* args);
0503 
0504     //! Displays debug information about all actions collected by the handler.
0505     void debug();
0506 
0507     /*! Like execute() with simulate set to true, but debug is directed to debugString.
0508      This function is used only in the alter table test suite. */
0509 //  tristate simulateExecution(const QString& tableName, QString& debugString);
0510 
0511     /*! Helper. @return a combination of AlteringRequirements values decribing altering type required
0512      when a given property field's @a propertyName is altered.
0513      Used internally KDbAlterTableHandler. Moreover it can be also used in the Table Designer's code
0514      as a temporary replacement before KDbAlterTableHandler is fully implemented.
0515      Thus, it is possible to identify properties that have no PhysicalAlteringRequired flag set
0516      (e.g. caption or extended properties like visibleDecimalPlaces. */
0517     static int alteringTypeForProperty(const QByteArray& propertyName);
0518 
0519 private:
0520     Q_DISABLE_COPY(KDbAlterTableHandler)
0521     class Private;
0522     Private * const d;
0523 };
0524 
0525 #endif