KDb

KDbAlter.cpp
1 /* This file is part of the KDE project
2  Copyright (C) 2006-2012 JarosÅ‚aw Staniek <[email protected]>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later version.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18 */
19 
20 #include "KDbAlter.h"
21 #include "KDb.h"
22 #include "KDbConnection.h"
23 #include "KDbConnectionOptions.h"
24 #include "kdb_debug.h"
25 
26 #include <QMap>
27 
28 #include <stdlib.h>
29 
30 class Q_DECL_HIDDEN KDbAlterTableHandler::Private
31 {
32 public:
33  Private() {}
34  ~Private() {
35  qDeleteAll(actions);
36  }
37  ActionList actions;
38 //! @todo IMPORTANT: replace QPointer<KDbConnection> conn;
39  KDbConnection* conn;
40 private:
41  Q_DISABLE_COPY(Private)
42 };
43 
44 //! Define a global instance used to when returning null is needed
45 #define DEFINE_NULL_OBJECT(name) \
46 class Null ## name : public KDbAlterTableHandler::name \
47 { \
48  public: \
49  Null ## name() : KDbAlterTableHandler::name(true) {} \
50 }; \
51 Null ## name null ## name
52 
53 DEFINE_NULL_OBJECT(ChangeFieldPropertyAction);
54 DEFINE_NULL_OBJECT(RemoveFieldAction);
55 DEFINE_NULL_OBJECT(InsertFieldAction);
56 DEFINE_NULL_OBJECT(MoveFieldPositionAction);
57 
58 //--------------------------------------------------------
59 
61  : m_alteringRequirements(0)
62  , m_order(-1)
63  , m_null(null)
64 {
65 }
66 
67 KDbAlterTableHandler::ActionBase::~ActionBase()
68 {
69 }
70 
71 KDbAlterTableHandler::ChangeFieldPropertyAction& KDbAlterTableHandler::ActionBase::toChangeFieldPropertyAction()
72 {
73  if (dynamic_cast<ChangeFieldPropertyAction*>(this))
74  return *dynamic_cast<ChangeFieldPropertyAction*>(this);
75  return nullChangeFieldPropertyAction;
76 }
77 
78 KDbAlterTableHandler::RemoveFieldAction& KDbAlterTableHandler::ActionBase::toRemoveFieldAction()
79 {
80  if (dynamic_cast<RemoveFieldAction*>(this))
81  return *dynamic_cast<RemoveFieldAction*>(this);
82  return nullRemoveFieldAction;
83 }
84 
85 KDbAlterTableHandler::InsertFieldAction& KDbAlterTableHandler::ActionBase::toInsertFieldAction()
86 {
87  if (dynamic_cast<InsertFieldAction*>(this))
88  return *dynamic_cast<InsertFieldAction*>(this);
89  return nullInsertFieldAction;
90 }
91 
92 KDbAlterTableHandler::MoveFieldPositionAction& KDbAlterTableHandler::ActionBase::toMoveFieldPositionAction()
93 {
94  if (dynamic_cast<MoveFieldPositionAction*>(this))
95  return *dynamic_cast<MoveFieldPositionAction*>(this);
96  return nullMoveFieldPositionAction;
97 }
98 
100 {
101  kdbDebug() << debugString(debugOptions)
102  << " (req = " << alteringRequirements() << ")";
103 }
104 
105 //--------------------------------------------------------
106 
107 KDbAlterTableHandler::FieldActionBase::FieldActionBase(const QString& fieldName, int uid)
108  : ActionBase(false)
109  , m_fieldUID(uid)
110  , m_fieldName(fieldName)
111 {
112 }
113 
114 KDbAlterTableHandler::FieldActionBase::FieldActionBase(bool)
115  : ActionBase(true)
116  , m_fieldUID(-1)
117 {
118 }
119 
120 KDbAlterTableHandler::FieldActionBase::~FieldActionBase()
121 {
122 }
123 
124 //--------------------------------------------------------
125 
126 //! @internal
127 struct KDb_AlterTableHandlerStatic {
128  KDb_AlterTableHandlerStatic() {
129 #define I(name, type) \
130  types.insert(QByteArray(name).toLower(), int(KDbAlterTableHandler::type))
131 #define I2(name, type1, type2) \
132  flag = int(KDbAlterTableHandler::type1)|int(KDbAlterTableHandler::type2); \
133  if (flag & KDbAlterTableHandler::PhysicalAlteringRequired) \
134  flag |= KDbAlterTableHandler::MainSchemaAlteringRequired; \
135  types.insert(QByteArray(name).toLower(), flag)
136 
137  /* useful links:
138  https://dev.mysql.com/doc/refman/5.0/en/create-table.html
139  */
140  // ExtendedSchemaAlteringRequired is here because when the field is renamed,
141  // we need to do the same rename in extended table schema: <field name="...">
142  int flag;
145  I("caption", MainSchemaAlteringRequired);
146  I("description", MainSchemaAlteringRequired);
147  I2("unsigned", PhysicalAlteringRequired, DataConversionRequired); // always?
148  I2("maxLength", PhysicalAlteringRequired, DataConversionRequired); // always?
149  I2("precision", PhysicalAlteringRequired, DataConversionRequired); // always?
150  I("defaultWidth", ExtendedSchemaAlteringRequired);
151  // defaultValue: depends on backend, for mysql it can only by a constant or now()...
152  // -- should we look at KDbDriver here?
153 #ifdef KDB_UNFINISHED
155 #else
156  //! @todo reenable
157  I("defaultValue", MainSchemaAlteringRequired);
158 #endif
160  I2("unique", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here
161  I2("notNull", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here
162  // allowEmpty: only support it just at kexi level? maybe there is a backend that supports this?
164  I2("autoIncrement", PhysicalAlteringRequired, DataConversionRequired); // data conversion may be hard here
165  I2("indexed", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here
166 
167  // easier cases follow...
168  I("visibleDecimalPlaces", ExtendedSchemaAlteringRequired);
169 
170  //more to come...
171 #undef I
172 #undef I2
173  }
174 
176 };
177 
178 Q_GLOBAL_STATIC(KDb_AlterTableHandlerStatic, KDb_alteringTypeForProperty)
179 
180 //! @internal
182 {
183  const int res = KDb_alteringTypeForProperty->types[propertyName.toLower()];
184  if (res == 0) {
185  if (KDb::isExtendedTableFieldProperty(propertyName))
186  return int(ExtendedSchemaAlteringRequired);
187  kdbWarning() << "property" << propertyName << "not found!";
188  }
189  return res;
190 }
191 
192 //---
193 
195  const QString& fieldName, const QString& propertyName, const QVariant& newValue, int uid)
196  : FieldActionBase(fieldName, uid)
197  , m_propertyName(propertyName)
198  , m_newValue(newValue)
199 {
200 }
201 
204 {
205 }
206 
208  : FieldActionBase(null)
209 {
210 }
211 
212 KDbAlterTableHandler::ChangeFieldPropertyAction::~ChangeFieldPropertyAction()
213 {
214 }
215 
216 void KDbAlterTableHandler::ChangeFieldPropertyAction::updateAlteringRequirements()
217 {
218  setAlteringRequirements(alteringTypeForProperty(m_propertyName.toLatin1()));
219 }
220 
221 QString KDbAlterTableHandler::ChangeFieldPropertyAction::debugString(const DebugOptions& debugOptions)
222 {
223  QString s = QString::fromLatin1("Set \"%1\" property for table field \"%2\" to \"%3\"")
224  .arg(m_propertyName, fieldName(), m_newValue.toString());
225  if (debugOptions.showUID) {
226  s.append(QString::fromLatin1(" (UID=%1)").arg(m_fieldUID));
227  }
228  return s;
229 }
230 
231 static KDbAlterTableHandler::ActionDict* createActionDict(
232  KDbAlterTableHandler::ActionDictDict *fieldActions, int forFieldUID)
233 {
235  fieldActions->insert(forFieldUID, dict);
236  return dict;
237 }
238 
239 static void debugAction(KDbAlterTableHandler::ActionBase *action, int nestingLevel,
240  bool simulate, const QString& prependString = QString(), QString * debugTarget = nullptr)
241 {
242  QString debugString;
243  if (!debugTarget)
244  debugString = prependString;
245  if (action) {
247  debugOptions.showUID = debugTarget == nullptr;
248  debugOptions.showFieldDebug = debugTarget != nullptr;
249  debugString += action->debugString(debugOptions);
250  } else {
251  if (!debugTarget) {
252  debugString += QLatin1String("[No action]"); //hmm
253  }
254  }
255  if (debugTarget) {
256  if (!debugString.isEmpty()) {
257  *debugTarget += debugString + QLatin1Char('\n');
258  }
259  } else {
260  kdbDebug() << debugString;
261 #ifdef KDB_DEBUG_GUI
262  if (simulate)
263  KDb::alterTableActionDebugGUI(debugString, nestingLevel);
264 #else
265  Q_UNUSED(simulate)
266  Q_UNUSED(nestingLevel)
267 #endif
268  }
269 }
270 
271 static void debugActionDict(KDbAlterTableHandler::ActionDict *dict, int fieldUID, bool simulate)
272 {
273  QString fieldName;
274  KDbAlterTableHandler::ActionDictConstIterator it(dict->constBegin());
275  if (it != dict->constEnd() && dynamic_cast<KDbAlterTableHandler::FieldActionBase*>(it.value())) {
276  //retrieve field name from the 1st related action
277  fieldName = dynamic_cast<KDbAlterTableHandler::FieldActionBase*>(it.value())->fieldName();
278  }
279  else {
280  fieldName = QLatin1String("??");
281  }
282  QString dbg(QString::fromLatin1("Action dict for field \"%1\" (%2, UID=%3):")
283  .arg(fieldName).arg(dict->count()).arg(fieldUID));
284  kdbDebug() << dbg;
285 #ifdef KDB_DEBUG_GUI
286  if (simulate)
287  KDb::alterTableActionDebugGUI(dbg, 1);
288 #endif
289  for (;it != dict->constEnd(); ++it) {
290  debugAction(it.value(), 2, simulate);
291  }
292 }
293 
294 static void debugFieldActions(const KDbAlterTableHandler::ActionDictDict &fieldActions, bool simulate)
295 {
296 #ifdef KDB_DEBUG_GUI
297  if (simulate)
298  KDb::alterTableActionDebugGUI(QLatin1String("** Simplified Field Actions:"));
299 #endif
300  for (KDbAlterTableHandler::ActionDictDictConstIterator it(fieldActions.constBegin()); it != fieldActions.constEnd(); ++it) {
301  debugActionDict(it.value(), it.key(), simulate);
302  }
303 }
304 
305 /*!
306  Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation.
307  Case 1. (special)
308  when new action=[rename A to B]
309  and exists=[rename B to C]
310  =>
311  remove [rename B to C]
312  and set result to new [rename A to C]
313  and go to 1b.
314  Case 1b. when new action=[rename A to B]
315  and actions exist like [set property P to C in field B]
316  or like [delete field B]
317  or like [move field B]
318  =>
319  change B to A for all these actions
320  Case 2. when new action=[change property in field A] (property != name)
321  and exists=[remove A] or exists=[change property in field A]
322  =>
323  do not add [change property in field A] because it will be removed anyway or the property will change
324 */
326 {
327  ActionDict *actionsLikeThis = fieldActions->value(uid());
328  if (m_propertyName == QLatin1String("name")) {
329  // Case 1. special: name1 -> name2, i.e. rename action
330  QByteArray newName(newValue().toString().toLatin1());
331  // try to find rename(newName, otherName) action
332  ActionBase *renameActionLikeThis = actionsLikeThis ? actionsLikeThis->value(newName) : nullptr;
333  if (dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)) {
334  // 1. instead of having rename(fieldName(), newValue()) action,
335  // let's have rename(fieldName(), otherName) action
336  m_newValue = dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue;
337  /* KDbAlterTableHandler::ChangeFieldPropertyAction* newRenameAction
338  = new KDbAlterTableHandler::ChangeFieldPropertyAction( *this );
339  newRenameAction->m_newValue = dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue;
340  // (m_order is the same as in newAction)
341  // replace prev. rename action (if any)
342  actionsLikeThis->remove( "name" );
343  ActionDict *adict = (*fieldActions)[ fieldName().toLatin1() ];
344  if (!adict)
345  adict = createActionDict( fieldActions, fieldName() );
346  adict->insert(m_propertyName.toLatin1(), newRenameAction);*/
347  } else {
348  ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->value(":remove:") : nullptr;
349  if (removeActionForThisField) {
350  //if this field is going to be removed, just change the action's field name
351  // and do not add a new action
352  } else {
353  //just insert a copy of the rename action
354  if (!actionsLikeThis)
355  actionsLikeThis = createActionDict(fieldActions, uid());
358  kdbDebug() << "insert into" << fieldName() << "dict:" << newRenameAction->debugString();
359  actionsLikeThis->insert(m_propertyName.toLatin1(), newRenameAction);
360  return;
361  }
362  }
363  if (actionsLikeThis) {
364  // Case 1b. change "field name" information to fieldName() in any action that
365  // is related to newName
366  // e.g. if there is setCaption("B", "captionA") action after rename("A","B"),
367  // replace setCaption action with setCaption("A", "captionA")
368  foreach(ActionBase* action, *actionsLikeThis) {
369  dynamic_cast<FieldActionBase*>(action)->setFieldName(fieldName());
370  }
371  }
372  return;
373  }
374  ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->value(":remove:") : nullptr;
375  if (removeActionForThisField) {
376  //if this field is going to be removed, do not add a new action
377  return;
378  }
379  // Case 2. other cases: just give up with adding this "intermediate" action
380  // so, e.g. [ setCaption(A, "captionA"), setCaption(A, "captionB") ]
381  // becomes: [ setCaption(A, "captionB") ]
382  // because adding this action does nothing
383  ActionDict *nextActionsLikeThis = fieldActions->value(uid());
384  if (!nextActionsLikeThis || !nextActionsLikeThis->value(m_propertyName.toLatin1())) {
385  //no such action, add this
388  if (!nextActionsLikeThis)
389  nextActionsLikeThis = createActionDict(fieldActions, uid());
390  nextActionsLikeThis->insert(m_propertyName.toLatin1(), newAction);
391  }
392 }
393 
395 {
396  Q_UNUSED(fieldActions);
397  return 0 == fieldName().compare(m_newValue.toString(), Qt::CaseInsensitive);
398 }
399 
400 tristate KDbAlterTableHandler::ChangeFieldPropertyAction::updateTableSchema(KDbTableSchema* table, KDbField* field,
401  QHash<QString, QString>* fieldHash)
402 {
403  //1. Simpler cases first: changes that do not affect table schema at all
404  // "caption", "description", "defaultWidth", "visibleDecimalPlaces"
405  if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.toLatin1())) {
406  bool result = KDb::setFieldProperty(field, m_propertyName.toLatin1(), newValue());
407  return result;
408  }
409 
410  if (m_propertyName == QLatin1String("name")) {
411  if (fieldHash->value(field->name()) == field->name())
412  fieldHash->remove(field->name());
413  fieldHash->insert(newValue().toString(), field->name());
414  (void)table->renameField(field, newValue().toString());
415  return true;
416  }
417  return cancelled;
418 }
419 
420 /*! Many of the properties must be applied using a separate algorithm.
421 */
423 {
424  Q_UNUSED(conn);
425  KDbField *field = table->field(fieldName());
426  if (!field) {
427  //! @todo errmsg
428  return false;
429  }
430  bool result;
431  //1. Simpler cases first: changes that do not affect table schema at all
432  // "caption", "description", "defaultWidth", "visibleDecimalPlaces"
433  if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.toLatin1())) {
434  result = KDb::setFieldProperty(field, m_propertyName.toLatin1(), newValue());
435  return result;
436  }
437 
438 //! @todo
439 #if 1
440  return true;
441 #else
442  //2. Harder cases, that often require special care
443  if (m_propertyName == QLatin1String("name")) {
444  /*mysql:
445  A. Get real field type (it's safer):
446  let <TYPE> be the 2nd "Type" column from result of "DESCRIBE tablename oldfieldname"
447  ( https://dev.mysql.com/doc/refman/5.0/en/describe.html )
448  B. Run "ALTER TABLE tablename CHANGE oldfieldname newfieldname <TYPE>";
449  ( https://dev.mysql.com/doc/refman/5.0/en/alter-table.html )
450  */
451  }
452  if (m_propertyName == QLatin1String("type")) {
453  /*mysql:
454  A. Like A. for "name" property above
455  B. Construct <TYPE> string, eg. "varchar(50)" using the driver
456  C. Like B. for "name" property above
457  (mysql then truncate the values for changes like varchar -> integer,
458  and properly convert the values for changes like integer -> varchar)
459  */
460  //! @todo more cases to check
461  }
462  if (m_propertyName == QLatin1String("maxLength")) {
463  //! @todo use "select max( length(o_name) ) from kexi__objects"
464 
465  }
466  if (m_propertyName == QLatin1String("primaryKey")) {
467 //! @todo
468  }
469 
470  /*
471  "name", "unsigned", "precision",
472  "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty",
473  "autoIncrement", "indexed",
474 
475 
476  bool result = KDb::setFieldProperty(*field, m_propertyName.toLatin1(), newValue());
477  */
478  return result;
479 #endif
480 }
481 
482 //--------------------------------------------------------
483 
484 KDbAlterTableHandler::RemoveFieldAction::RemoveFieldAction(const QString& fieldName, int uid)
485  : FieldActionBase(fieldName, uid)
486 {
487 }
488 
489 KDbAlterTableHandler::RemoveFieldAction::RemoveFieldAction(bool null)
490  : FieldActionBase(null)
491 {
492 }
493 
494 KDbAlterTableHandler::RemoveFieldAction::~RemoveFieldAction()
495 {
496 }
497 
499 {
500 //! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ?
501 
502  setAlteringRequirements(PhysicalAlteringRequired);
503  //! @todo
504 }
505 
506 QString KDbAlterTableHandler::RemoveFieldAction::debugString(const DebugOptions& debugOptions)
507 {
508  QString s = QString::fromLatin1("Delete table field \"%1\"").arg(fieldName());
509  if (debugOptions.showUID) {
510  s.append(QString::fromLatin1(" (UID=%1)").arg(uid()));
511  }
512  return s;
513 }
514 
515 /*!
516  Legend: A,B==objects, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation.
517  Preconditions: we assume there cannot be such case encountered: ([remove A], [do something related on A])
518  (except for [remove A], [insert A])
519  General Case: it's safe to always insert a [remove A] action.
520 */
522 {
523  //! @todo not checked
526  ActionDict *actionsLikeThis = fieldActions->value(uid());
527  if (!actionsLikeThis)
528  actionsLikeThis = createActionDict(fieldActions, uid());
529  actionsLikeThis->insert(":remove:", newAction); //special
530 }
531 
532 tristate KDbAlterTableHandler::RemoveFieldAction::updateTableSchema(KDbTableSchema* table, KDbField* field,
533  QHash<QString, QString>* fieldHash)
534 {
535  fieldHash->remove(field->name());
536  table->removeField(field);
537  return true;
538 }
539 
541 {
542  Q_UNUSED(conn);
543  Q_UNUSED(table);
544  //! @todo
545  return true;
546 }
547 
548 //--------------------------------------------------------
549 
551  : FieldActionBase(field->name(), uid)
552  , m_index(fieldIndex)
553  , m_field(nullptr)
554 {
555  Q_ASSERT(field);
556  setField(field);
557 }
558 
560  : FieldActionBase(action) //action.fieldName(), action.uid())
561  , m_index(action.index())
562 {
563  m_field = new KDbField(*action.field());
564 }
565 
567  : InsertFieldAction(true)
568 {
569 }
570 
572  : FieldActionBase(null)
573  , m_index(0)
574  , m_field(nullptr)
575 {
576 }
577 
578 KDbAlterTableHandler::InsertFieldAction::~InsertFieldAction()
579 {
580  delete m_field;
581 }
582 
583 void KDbAlterTableHandler::InsertFieldAction::setField(KDbField* field)
584 {
585  if (m_field)
586  delete m_field;
587  m_field = field;
588  setFieldName(m_field ? m_field->name() : QString());
589 }
590 
592 {
593 //! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ?
594 
595  setAlteringRequirements(PhysicalAlteringRequired);
596  //! @todo
597 }
598 
599 QString KDbAlterTableHandler::InsertFieldAction::debugString(const DebugOptions& debugOptions)
600 {
601  QString s = QString::fromLatin1("Insert table field \"%1\" at position %2")
602  .arg(m_field->name()).arg(m_index);
603  if (debugOptions.showUID) {
604  s.append(QString::fromLatin1(" (UID=%1)").arg(m_fieldUID));
605  }
606  if (debugOptions.showFieldDebug) {
607  s.append(QString::fromLatin1(" (%1)").arg(KDbUtils::debugString<KDbField>(*m_field)));
608  }
609  return s;
610 }
611 
612 /*!
613  Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation.
614 
615 
616  Case 1: there are "change property" actions after the Insert action.
617  -> change the properties in the Insert action itself and remove the "change property" actions.
618  Examples:
619  [Insert A] && [rename A to B] => [Insert B]
620  [Insert A] && [change property P in field A] => [Insert A with P altered]
621  Comment: we need to do this reduction because otherwise we'd need to do psyhical altering
622  right after [Insert A] if [rename A to B] follows.
623 */
625 {
626  // Try to find actions related to this action
627  ActionDict *actionsForThisField = fieldActions->value(uid());
628 
629  ActionBase *removeActionForThisField = actionsForThisField ? actionsForThisField->value(":remove:") : nullptr;
630  if (removeActionForThisField) {
631  //if this field is going to be removed, do not add a new action
632  //and remove the "Remove" action
633  actionsForThisField->remove(":remove:");
634  return;
635  }
636  if (actionsForThisField) {
637  //collect property values that have to be changed in this field
639  ActionDict *newActionsForThisField = new ActionDict(); // this will replace actionsForThisField after the loop
640  QSet<ActionBase*> actionsToDelete; // used to collect actions taht we soon delete but cannot delete in the loop below
641  for (ActionDictConstIterator it(actionsForThisField->constBegin()); it != actionsForThisField->constEnd();++it) {
642  ChangeFieldPropertyAction* changePropertyAction = dynamic_cast<ChangeFieldPropertyAction*>(it.value());
643  if (changePropertyAction) {
644  //if this field is going to be renamed, also update fieldName()
645  if (changePropertyAction->propertyName() == QLatin1String("name")) {
646  setFieldName(changePropertyAction->newValue().toString());
647  }
648  values.insert(changePropertyAction->propertyName().toLatin1(), changePropertyAction->newValue());
649  //the subsequent "change property" action is no longer needed
650  actionsToDelete.insert(it.value());
651  } else {
652  //keep
653  newActionsForThisField->insert(it.key(), it.value());
654  }
655  }
656  qDeleteAll(actionsToDelete);
657  actionsForThisField->setAutoDelete(false);
658  delete actionsForThisField;
659  actionsForThisField = newActionsForThisField;
660  fieldActions->take(uid());
661  fieldActions->insert(uid(), actionsForThisField);
662  if (!values.isEmpty()) {
663  //update field, so it will be created as one step
664  KDbField *f = new KDbField(*field());
666  setField(f);
667  kdbDebug() << field();
668 #ifdef KDB_DEBUG_GUI
669  KDb::alterTableActionDebugGUI(
670  QLatin1String("** Property-set actions moved to field definition itself:\n")
671  + KDbUtils::debugString<KDbField>(*field()), 0);
672 #endif
673  } else {
674 #ifdef KDB_DEBUG_GUI
675  KDb::alterTableActionDebugGUI(
676  QLatin1String("** Failed to set properties for field ") + KDbUtils::debugString<KDbField>(*field()), 0);
677 #endif
678  kdbWarning() << "setFieldProperties() failed!";
679  delete f;
680  }
681  }
682  }
683  //ok, insert this action
684  //! @todo not checked
687  if (!actionsForThisField)
688  actionsForThisField = createActionDict(fieldActions, uid());
689  actionsForThisField->insert(":insert:", newAction); //special
690 }
691 
693  QHash<QString, QString>* fieldMap)
694 {
695  //in most cases we won't add the field to fieldMap
696  Q_UNUSED(field);
697 //! @todo add it only when there should be fixed value (e.g. default) set for this new field...
698  fieldMap->remove(this->field()->name());
699  table->insertField(index(), new KDbField(*this->field()));
700  return true;
701 }
702 
704 {
705  Q_UNUSED(conn);
706  Q_UNUSED(table);
707  //! @todo
708  return true;
709 }
710 
711 //--------------------------------------------------------
712 
713 KDbAlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(
714  int fieldIndex, const QString& fieldName, int uid)
715  : FieldActionBase(fieldName, uid)
716  , m_index(fieldIndex)
717 {
718 }
719 
720 KDbAlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(bool null)
721  : FieldActionBase(null)
722  , m_index(-1)
723 {
724 }
725 
726 KDbAlterTableHandler::MoveFieldPositionAction::~MoveFieldPositionAction()
727 {
728 }
729 
731 {
732  setAlteringRequirements(MainSchemaAlteringRequired);
733  //! @todo
734 }
735 
736 QString KDbAlterTableHandler::MoveFieldPositionAction::debugString(const DebugOptions& debugOptions)
737 {
738  QString s = QString::fromLatin1("Move table field \"%1\" to position %2")
739  .arg(fieldName()).arg(m_index);
740  if (debugOptions.showUID) {
741  s.append(QString::fromLatin1(" (UID=%1)").arg(uid()));
742  }
743  return s;
744 }
745 
747 {
748  Q_UNUSED(fieldActions);
749  //! @todo
750 }
751 
753 {
754  Q_UNUSED(conn);
755  Q_UNUSED(table);
756  //! @todo
757  return true;
758 }
759 
760 //--------------------------------------------------------
761 
762 KDbAlterTableHandler::KDbAlterTableHandler(KDbConnection* conn)
763  : d(new Private())
764 {
765  d->conn = conn;
766 }
767 
768 KDbAlterTableHandler::~KDbAlterTableHandler()
769 {
770  delete d;
771 }
772 
774 {
775  d->actions.append(action);
776 }
777 
779 {
780  d->actions.append(action);
781  return *this;
782 }
783 
785 {
786  return d->actions;
787 }
788 
790 {
791  d->actions.removeAt(index);
792 }
793 
795 {
796  d->actions.clear();
797 }
798 
800 {
801  qDeleteAll(d->actions);
802  d->actions = actions;
803 }
804 
806 {
807  kdbDebug() << "KDbAlterTableHandler's actions:";
808  foreach(ActionBase* action, d->actions) {
809  action->debug();
810  }
811 }
812 
814 {
815  args->result = false;
816  if (!d->conn) {
817 //! @todo err msg?
818  return nullptr;
819  }
820  if (d->conn->options()->isReadOnly()) {
821 //! @todo err msg?
822  return nullptr;
823  }
824  if (!d->conn->isDatabaseUsed()) {
825 //! @todo err msg?
826  return nullptr;
827  }
828  KDbTableSchema *oldTable = d->conn->tableSchema(tableName);
829  if (!oldTable) {
830 //! @todo err msg?
831  return nullptr;
832  }
833 
834  if (!args->debugString)
835  debug();
836 
837  // Find a sum of requirements...
838  int allActionsCount = 0;
839  foreach(ActionBase* action, d->actions) {
840  action->updateAlteringRequirements();
841  action->m_order = allActionsCount++;
842  }
843 
844  /* Simplify actions list if possible and check for errors
845 
846  How to do it?
847  - track property changes/deletions in reversed order
848  - reduce intermediate actions
849 
850  Trivial example 1:
851  *action1: "rename field a to b"
852  *action2: "rename field b to c"
853  *action3: "rename field c to d"
854 
855  After reduction:
856  *action1: "rename field a to d"
857  Summing up: we have tracked what happens to field curently named "d"
858  and eventually discovered that it was originally named "a".
859 
860  Trivial example 2:
861  *action1: "rename field a to b"
862  *action2: "rename field b to c"
863  *action3: "remove field b"
864  After reduction:
865  *action3: "remove field b"
866  Summing up: we have noticed that field "b" has beed eventually removed
867  so we needed to find all actions related to this field and remove them.
868  This is good optimization, as some of the eventually removed actions would
869  be difficult to perform and/or costly, what would be a waste of resources
870  and a source of unwanted questions sent to the user.
871  */
872 
873 
874 
875  // Fields-related actions.
876  ActionDictDict fieldActions;
877  ActionBase* action;
878  for (int i = d->actions.count() - 1; i >= 0; i--) {
879  d->actions[i]->simplifyActions(&fieldActions);
880  }
881 
882  if (!args->debugString)
883  debugFieldActions(fieldActions, args->simulate);
884 
885  // Prepare actions for execution ----
886  // - Sort actions by order
887  ActionsVector actionsVector(allActionsCount);
888  int currentActionsCount = 0; //some actions may be removed
889  args->requirements = 0;
890  QSet<QString> fieldsWithChangedMainSchema; // Used to collect fields with changed main schema.
891  // This will be used when recreateTable is false to update kexi__fields
892  for (ActionDictDictConstIterator it(fieldActions.constBegin()); it != fieldActions.constEnd(); ++it) {
893  for (KDbAlterTableHandler::ActionDictConstIterator it2(it.value()->constBegin());
894  it2 != it.value()->constEnd(); ++it2, currentActionsCount++)
895  {
896  if (it2.value()->shouldBeRemoved(&fieldActions))
897  continue;
898  actionsVector[ it2.value()->m_order ] = it2.value();
899  // a sum of requirements...
900  const int r = it2.value()->alteringRequirements();
901  args->requirements |= r;
902  if (r & MainSchemaAlteringRequired && dynamic_cast<ChangeFieldPropertyAction*>(it2.value())) {
903  // Remember, this will be used when recreateTable is false to update kexi__fields, below.
904  fieldsWithChangedMainSchema.insert(
905  dynamic_cast<ChangeFieldPropertyAction*>(it2.value())->fieldName());
906  }
907  }
908  }
909  // - Debug
910  QString dbg = QString::fromLatin1("** Overall altering requirements: %1").arg(args->requirements);
911  kdbDebug() << dbg;
912 
913  if (args->onlyComputeRequirements) {
914  args->result = true;
915  return nullptr;
916  }
917 
918  const bool recreateTable = (args->requirements & PhysicalAlteringRequired);
919 
920 #ifdef KDB_DEBUG_GUI
921  if (args->simulate)
922  KDb::alterTableActionDebugGUI(dbg, 0);
923 #endif
924  dbg = QString::fromLatin1("** Ordered, simplified actions (%1, was %2):")
925  .arg(currentActionsCount).arg(allActionsCount);
926  kdbDebug() << dbg;
927 #ifdef KDB_DEBUG_GUI
928  if (args->simulate)
929  KDb::alterTableActionDebugGUI(dbg, 0);
930 #endif
931  for (int i = 0; i < allActionsCount; i++) {
932  debugAction(actionsVector.at(i), 1, args->simulate,
933  QString::fromLatin1("%1: ").arg(i + 1), args->debugString);
934  }
935 
936  if (args->requirements == 0) {//nothing to do
937  args->result = true;
938  return oldTable;
939  }
940  if (args->simulate) {//do not execute
941  args->result = true;
942  return oldTable;
943  }
944 //! @todo transaction!
945 
946  // Create a new KDbTableSchema
947  KDbTableSchema *newTable = recreateTable ? new KDbTableSchema(*oldTable, false/*!copy id*/) : oldTable;
948  // find nonexisting temp name for new table schema
949  if (recreateTable) {
950  QString tempDestTableName = KDb::temporaryTableName(d->conn, newTable->name());
951  newTable->setName(tempDestTableName);
952  }
953  kdbDebug() << *oldTable;
954  if (recreateTable && !args->debugString) {
955  kdbDebug() << *newTable;
956  }
957 
958  // Update table schema in memory ----
959  int lastUID = -1;
960  KDbField *currentField = nullptr;
961  QHash<QString, QString> fieldHash; // a map from new value to old value
962  foreach(KDbField* f, *newTable->fields()) {
963  fieldHash.insert(f->name(), f->name());
964  }
965  for (int i = 0; i < allActionsCount; i++) {
966  action = actionsVector.at(i);
967  if (!action)
968  continue;
969  //remember the current KDbField object because soon we may be unable to find it by name:
970  FieldActionBase *fieldAction = dynamic_cast<FieldActionBase*>(action);
971  if (!fieldAction) {
972  currentField = nullptr;
973  } else {
974  if (lastUID != fieldAction->uid()) {
975  currentField = newTable->field(fieldAction->fieldName());
976  lastUID = currentField ? fieldAction->uid() : -1;
977  }
978  InsertFieldAction *insertFieldAction = dynamic_cast<InsertFieldAction*>(action);
979  if (insertFieldAction && insertFieldAction->index() > newTable->fieldCount()) {
980  //update index: there can be empty rows
981  insertFieldAction->setIndex(newTable->fieldCount());
982  }
983  }
984  args->result = action->updateTableSchema(newTable, currentField, &fieldHash);
985  if (args->result != true) {
986  if (recreateTable)
987  delete newTable;
988  return nullptr;
989  }
990  }
991 
992  if (recreateTable) {
993  // Create the destination table with temporary name
994  if (!d->conn->createTable(newTable,
995  KDbConnection::CreateTableOptions(KDbConnection::CreateTableOption::Default)
997  {
998  m_result = d->conn->result();
999  delete newTable;
1000  args->result = false;
1001  return nullptr;
1002  }
1003  }
1004 
1005 #if 0
1006  //! @todo
1007  // Execute actions ----
1008  for (int i = 0; i < allActionsCount; i++) {
1009  action = actionsVector.at(i);
1010  if (!action)
1011  continue;
1012  args.result = action->execute(*d->conn, *newTable);
1013  if (!args.result || ~args.result) {
1014 //! @todo delete newTable...
1015  args.result = false;
1016  return 0;
1017  }
1018  }
1019 #endif
1020 
1021  // update extended table schema after executing the actions
1022  if (!d->conn->storeExtendedTableSchemaData(newTable)) {
1023 //! @todo better errmsg?
1024  m_result = d->conn->result();
1025 //! @todo delete newTable...
1026  args->result = false;
1027  return nullptr;
1028  }
1029 
1030  if (recreateTable) {
1031  // Copy the data:
1032  // Build "INSERT INTO ... SELECT FROM ..." SQL statement
1033  // The order is based on the order of the source table fields.
1034  // Notes:
1035  // -Some source fields can be skipped in case when there are deleted fields.
1036  // -Some destination fields can be skipped in case when there
1037  // are new empty fields without fixed/default value.
1038  KDbEscapedString sql = KDbEscapedString("INSERT INTO %1 (").arg(d->conn->escapeIdentifier(newTable->name()));
1039  //insert list of dest. fields
1040  bool first = true;
1041  KDbEscapedString sourceFields;
1042  foreach(KDbField* f, *newTable->fields()) {
1043  QString renamedFieldName(fieldHash.value(f->name()));
1044  KDbEscapedString sourceSqlString;
1045  const KDbField::Type type = f->type(); // cache: evaluating type of expressions can be expensive
1046  if (!renamedFieldName.isEmpty()) {
1047  //this field should be renamed
1048  sourceSqlString = KDbEscapedString(d->conn->escapeIdentifier(renamedFieldName));
1049  } else if (!f->defaultValue().isNull()) {
1050  //this field has a default value defined
1051 //! @todo support expressions (eg. TODAY()) as a default value
1052 //! @todo this field can be notNull or notEmpty - check whether the default is ok
1053 //! (or do this checking also in the Table Designer?)
1054  sourceSqlString = d->conn->driver()->valueToSql(type, f->defaultValue());
1055  } else if (f->isNotNull()) {
1056  //this field cannot be null
1057  sourceSqlString = d->conn->driver()->valueToSql(
1058  type, KDb::emptyValueForFieldType(type));
1059  } else if (f->isNotEmpty()) {
1060  //this field cannot be empty - use any nonempty value..., e.g. " " for text or 0 for number
1061  sourceSqlString = d->conn->driver()->valueToSql(
1062  type, KDb::notEmptyValueForFieldType(type));
1063  }
1064 //! @todo support unique, validatationRule, unsigned flags...
1065 //! @todo check for foreignKey values...
1066 
1067  if (!sourceSqlString.isEmpty()) {
1068  if (first) {
1069  first = false;
1070  } else {
1071  sql.append(", ");
1072  sourceFields.append(", ");
1073  }
1074  sql += d->conn->escapeIdentifier(f->name());
1075  sourceFields.append(sourceSqlString);
1076  }
1077  }
1078  sql += (") SELECT " + sourceFields + " FROM " + oldTable->name());
1079  kdbDebug() << " ** " << sql;
1080  if (!d->conn->executeSql(sql)) {
1081  m_result = d->conn->result();
1082 //! @todo delete newTable...
1083  args->result = false;
1084  return nullptr;
1085  }
1086 
1087  const QString oldTableName = oldTable->name();
1088  /* args.result = d->conn->dropTable( oldTable );
1089  if (!args.result || ~args.result) {
1090  setError(d->conn);
1091  //! @todo delete newTable...
1092  return 0;
1093  }
1094  oldTable = 0;*/
1095 
1096  // Replace the old table with the new one (oldTable will be destroyed)
1097  if (!d->conn->alterTableName(newTable, oldTableName,
1098  KDbConnection::AlterTableNameOption::Default | KDbConnection::AlterTableNameOption::DropDestination))
1099  {
1100  m_result = d->conn->result();
1101 //! @todo delete newTable...
1102  args->result = false;
1103  return nullptr;
1104  }
1105  oldTable = nullptr;
1106  }
1107 
1108  if (!recreateTable) {
1109  if ((MainSchemaAlteringRequired & args->requirements) && !fieldsWithChangedMainSchema.isEmpty()) {
1110  //update main schema (kexi__fields) for changed fields
1111  foreach(const QString& changeFieldPropertyActionName, fieldsWithChangedMainSchema) {
1112  KDbField *f = newTable->field(changeFieldPropertyActionName);
1113  if (f) {
1114  if (!d->conn->storeMainFieldSchema(f)) {
1115  m_result = d->conn->result();
1116  //! @todo delete newTable...
1117  args->result = false;
1118  return nullptr;
1119  }
1120  }
1121  }
1122  }
1123  }
1124  args->result = true;
1125  return newTable;
1126 }
1127 
1128 /*KDbTableSchema* KDbAlterTableHandler::execute(const QString& tableName, tristate &result, bool simulate)
1129 {
1130  return executeInternal( tableName, result, simulate, 0 );
1131 }
1132 
1133 tristate KDbAlterTableHandler::simulateExecution(const QString& tableName, QString& debugString)
1134 {
1135  tristate result;
1136  (void)executeInternal( tableName, result, true//simulate
1137  , &debugString );
1138  return result;
1139 }
1140 */
bool isNull() const const
KDbUtils::AutodeletedHash< QByteArray, ActionBase * > ActionDict
For collecting actions related to a single field.
Definition: KDbAlter.h:141
QVariant defaultValue() const
Definition: KDbField.cpp:281
const T value(const Key &key) const const
Abstract base class used for implementing all the AlterTable actions.
Definition: KDbAlter.h:158
bool isNotEmpty() const
Definition: KDbField.h:307
void removeAction(int index)
Definition: KDbAlter.cpp:789
CaseInsensitive
const ActionList & actions() const
Definition: KDbAlter.cpp:784
virtual KDbField * field(int id)
@ DropDestination
Drop destination table if exists.
Defines an action for removing a single table field.
Definition: KDbAlter.h:335
Specialized string for escaping.
QString name
bool showUID
true if UID should be added to the action debug string (the default)
Definition: KDbAlter.h:181
QByteArray toLatin1() const const
Abstract base class used for implementing table field-related actions.
Definition: KDbAlter.h:248
void simplifyActions(ActionDictDict *fieldActions) override
Definition: KDbAlter.cpp:624
bool shouldBeRemoved(ActionDictDict *fieldActions) override
Definition: KDbAlter.cpp:394
void simplifyActions(ActionDictDict *fieldActions) override
Definition: KDbAlter.cpp:325
KDB_EXPORT QVariant notEmptyValueForFieldType(KDbField::Type type)
Used in KDb::notEmptyValueForFieldType()
Definition: KDb.cpp:1275
@ ExtendedSchemaAlteringRequired
Definition: KDbAlter.h:135
KDB_EXPORT QVariant emptyValueForFieldType(KDbField::Type type)
Used in KDb::emptyValueForFieldType()
Definition: KDb.cpp:1222
Defines an action for inserting a single table field.
Definition: KDbAlter.h:360
QHash::iterator insert(const Key &key, const T &value)
Q_GLOBAL_STATIC(Internal::StaticControl, s_instance) class ControlPrivate
QStringList types(Mode mode=Writing)
InsertFieldAction()
Creates null action.
Definition: KDbAlter.cpp:566
KDB_EXPORT bool setFieldProperty(KDbField *field, const QByteArray &propertyName, const QVariant &value)
Definition: KDb.cpp:981
@ DropDestination
Drop destination table if exists.
T value(int i) const const
tristate execute(KDbConnection *conn, KDbTableSchema *table) override
Performs physical execution of this action.
Definition: KDbAlter.cpp:703
T take(const Key &key)
void addAction(ActionBase *action)
Definition: KDbAlter.cpp:773
KDbTableSchema * execute(const QString &tableName, ExecutionArguments *args)
Definition: KDbAlter.cpp:813
KDbAlterTableHandler & operator<<(ActionBase *action)
Definition: KDbAlter.cpp:778
QHash::const_iterator constBegin() const const
QHash::const_iterator constEnd() const const
void debug(const DebugOptions &debugOptions=DebugOptions())
Definition: KDbAlter.cpp:99
static int alteringTypeForProperty(const QByteArray &propertyName)
Definition: KDbAlter.cpp:181
const T & at(int i) const const
bool isEmpty() const const
bool removeField(KDbField *field) override
bool insertField(int index, KDbField *field) override
void setActions(const ActionList &actions)
Definition: KDbAlter.cpp:799
KDB_EXPORT bool setFieldProperties(KDbField *field, const QMap< QByteArray, QVariant > &values)
Definition: KDb.cpp:833
void debug()
Displays debug information about all actions collected by the handler.
Definition: KDbAlter.cpp:805
bool showFieldDebug
true if the field associated with the action (if exists) should be appended to the debug string (defa...
Definition: KDbAlter.h:185
Autodeleting hash.
Definition: KDbUtils.h:178
3-state logical type with three values: true, false and cancelled and convenient operators.
Definition: KDbTristate.h:100
tristate execute(KDbConnection *conn, KDbTableSchema *table) override
Performs physical execution of this action.
Definition: KDbAlter.cpp:422
Type type() const
Definition: KDbField.cpp:379
tristate execute(KDbConnection *conn, KDbTableSchema *table) override
Performs physical execution of this action.
Definition: KDbAlter.cpp:752
KDbField::List * fields()
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
int remove(const Key &key)
QString fromLatin1(const char *str, int size)
bool isNotNull() const
Definition: KDbField.h:302
KDB_EXPORT QString temporaryTableName(KDbConnection *conn, const QString &baseName)
Definition: KDb.cpp:2055
QSet::iterator insert(const T &value)
Meta-data for a field.
Definition: KDbField.h:71
int fieldCount() const
KDB_EXPORT bool isExtendedTableFieldProperty(const QByteArray &propertyName)
for isExtendedTableProperty()
Definition: KDb.cpp:954
bool renameField(const QString &oldName, const QString &newName)
int compare(const QString &other, Qt::CaseSensitivity cs) const const
Arguments for KDbAlterTableHandler::execute().
Definition: KDbAlter.h:458
int count(const Key &key) const const
Provides database connection, allowing queries and data modification.
Definition: KDbConnection.h:51
QString name() const
Definition: KDbField.cpp:256
tristate updateTableSchema(KDbTableSchema *table, KDbField *field, QHash< QString, QString > *fieldHash) override
Definition: KDbAlter.cpp:692
tristate execute(KDbConnection *conn, KDbTableSchema *table) override
Performs physical execution of this action.
Definition: KDbAlter.cpp:540
A tool for handling altering database table schema.
Definition: KDbAlter.h:109
QVector< V > values(const QMultiHash< K, V > &c)
Controls debug options for actions. Used in debugString() and debug().
Definition: KDbAlter.h:175
QString & append(QChar ch)
bool isEmpty() const const
void simplifyActions(ActionDictDict *fieldActions) override
Definition: KDbAlter.cpp:521
void simplifyActions(ActionDictDict *fieldActions) override
Definition: KDbAlter.cpp:746
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Thu Dec 7 2023 04:09:06 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.