KDb

KDbAlter.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2006-2012 Jarosław Staniek <staniek@kde.org>
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
30class Q_DECL_HIDDEN KDbAlterTableHandler::Private
31{
32public:
33 Private() {}
34 ~Private() {
35 qDeleteAll(actions);
36 }
37 ActionList actions;
38//! @todo IMPORTANT: replace QPointer<KDbConnection> conn;
39 KDbConnection* conn;
40private:
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) \
46class Null ## name : public KDbAlterTableHandler::name \
47{ \
48 public: \
49 Null ## name() : KDbAlterTableHandler::name(true) {} \
50}; \
51Null ## name null ## name
52
53DEFINE_NULL_OBJECT(ChangeFieldPropertyAction);
54DEFINE_NULL_OBJECT(RemoveFieldAction);
55DEFINE_NULL_OBJECT(InsertFieldAction);
56DEFINE_NULL_OBJECT(MoveFieldPositionAction);
57
58//--------------------------------------------------------
59
61 : m_alteringRequirements(0)
62 , m_order(-1)
63 , m_null(null)
64{
65}
66
67KDbAlterTableHandler::ActionBase::~ActionBase()
68{
69}
70
71KDbAlterTableHandler::ChangeFieldPropertyAction& KDbAlterTableHandler::ActionBase::toChangeFieldPropertyAction()
72{
73 if (dynamic_cast<ChangeFieldPropertyAction*>(this))
74 return *dynamic_cast<ChangeFieldPropertyAction*>(this);
75 return nullChangeFieldPropertyAction;
76}
77
78KDbAlterTableHandler::RemoveFieldAction& KDbAlterTableHandler::ActionBase::toRemoveFieldAction()
79{
80 if (dynamic_cast<RemoveFieldAction*>(this))
81 return *dynamic_cast<RemoveFieldAction*>(this);
82 return nullRemoveFieldAction;
83}
84
85KDbAlterTableHandler::InsertFieldAction& KDbAlterTableHandler::ActionBase::toInsertFieldAction()
86{
87 if (dynamic_cast<InsertFieldAction*>(this))
88 return *dynamic_cast<InsertFieldAction*>(this);
89 return nullInsertFieldAction;
90}
91
92KDbAlterTableHandler::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
107KDbAlterTableHandler::FieldActionBase::FieldActionBase(const QString& fieldName, int uid)
108 : ActionBase(false)
109 , m_fieldUID(uid)
110 , m_fieldName(fieldName)
111{
112}
113
114KDbAlterTableHandler::FieldActionBase::FieldActionBase(bool)
115 : ActionBase(true)
116 , m_fieldUID(-1)
117{
118}
119
120KDbAlterTableHandler::FieldActionBase::~FieldActionBase()
121{
122}
123
124//--------------------------------------------------------
125
126//! @internal
127struct 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;
143 I2("name", PhysicalAlteringRequired, MainSchemaAlteringRequired);
144 I2("type", PhysicalAlteringRequired, DataConversionRequired);
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
154 I2("defaultValue", PhysicalAlteringRequired, MainSchemaAlteringRequired);
155#else
156 //! @todo reenable
157 I("defaultValue", MainSchemaAlteringRequired);
158#endif
159 I2("primaryKey", PhysicalAlteringRequired, DataConversionRequired);
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?
163 I2("allowEmpty", PhysicalAlteringRequired, MainSchemaAlteringRequired);
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
178Q_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))
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
206
211
212KDbAlterTableHandler::ChangeFieldPropertyAction::~ChangeFieldPropertyAction()
213{
214}
215
216void KDbAlterTableHandler::ChangeFieldPropertyAction::updateAlteringRequirements()
217{
218 setAlteringRequirements(alteringTypeForProperty(m_propertyName.toLatin1()));
219}
220
221QString 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
231static KDbAlterTableHandler::ActionDict* createActionDict(
232 KDbAlterTableHandler::ActionDictDict *fieldActions, int forFieldUID)
233{
235 fieldActions->insert(forFieldUID, dict);
236 return dict;
237}
238
239static 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
271static 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
294static 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
400tristate 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
484KDbAlterTableHandler::RemoveFieldAction::RemoveFieldAction(const QString& fieldName, int uid)
485 : FieldActionBase(fieldName, uid)
486{
487}
488
489KDbAlterTableHandler::RemoveFieldAction::RemoveFieldAction(bool null)
490 : FieldActionBase(null)
491{
492}
493
494KDbAlterTableHandler::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
506QString 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
532tristate 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
570
572 : FieldActionBase(null)
573 , m_index(0)
574 , m_field(nullptr)
575{
576}
577
578KDbAlterTableHandler::InsertFieldAction::~InsertFieldAction()
579{
580 delete m_field;
581}
582
583void 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
599QString 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());
665 if (KDb::setFieldProperties(f, values)) {
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
713KDbAlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(
714 int fieldIndex, const QString& fieldName, int uid)
715 : FieldActionBase(fieldName, uid)
716 , m_index(fieldIndex)
717{
718}
719
720KDbAlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(bool null)
721 : FieldActionBase(null)
722 , m_index(-1)
723{
724}
725
726KDbAlterTableHandler::MoveFieldPositionAction::~MoveFieldPositionAction()
727{
728}
729
735
736QString 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
762KDbAlterTableHandler::KDbAlterTableHandler(KDbConnection* conn)
763 : d(new Private())
764{
765 d->conn = conn;
766}
767
768KDbAlterTableHandler::~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
1133tristate 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*/
Controls debug options for actions. Used in debugString() and debug().
Definition KDbAlter.h:176
bool showFieldDebug
true if the field associated with the action (if exists) should be appended to the debug string (defa...
Definition KDbAlter.h:185
bool showUID
true if UID should be added to the action debug string (the default)
Definition KDbAlter.h:181
Abstract base class used for implementing all the AlterTable actions.
Definition KDbAlter.h:159
void debug(const DebugOptions &debugOptions=DebugOptions())
Definition KDbAlter.cpp:99
tristate execute(KDbConnection *conn, KDbTableSchema *table) override
Performs physical execution of this action.
Definition KDbAlter.cpp:422
bool shouldBeRemoved(ActionDictDict *fieldActions) override
Definition KDbAlter.cpp:394
void simplifyActions(ActionDictDict *fieldActions) override
Definition KDbAlter.cpp:325
Arguments for KDbAlterTableHandler::execute().
Definition KDbAlter.h:459
Abstract base class used for implementing table field-related actions.
Definition KDbAlter.h:249
Defines an action for inserting a single table field.
Definition KDbAlter.h:361
InsertFieldAction()
Creates null action.
Definition KDbAlter.cpp:566
void simplifyActions(ActionDictDict *fieldActions) override
Definition KDbAlter.cpp:624
tristate execute(KDbConnection *conn, KDbTableSchema *table) override
Performs physical execution of this action.
Definition KDbAlter.cpp:703
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:752
void simplifyActions(ActionDictDict *fieldActions) override
Definition KDbAlter.cpp:746
Defines an action for removing a single table field.
Definition KDbAlter.h:336
tristate execute(KDbConnection *conn, KDbTableSchema *table) override
Performs physical execution of this action.
Definition KDbAlter.cpp:540
void simplifyActions(ActionDictDict *fieldActions) override
Definition KDbAlter.cpp:521
A tool for handling altering database table schema.
Definition KDbAlter.h:110
void setActions(const ActionList &actions)
Definition KDbAlter.cpp:799
void addAction(ActionBase *action)
Definition KDbAlter.cpp:773
void debug()
Displays debug information about all actions collected by the handler.
Definition KDbAlter.cpp:805
KDbUtils::AutodeletedHash< QByteArray, ActionBase * > ActionDict
For collecting actions related to a single field.
Definition KDbAlter.h:143
const ActionList & actions() const
Definition KDbAlter.cpp:784
void removeAction(int index)
Definition KDbAlter.cpp:789
KDbTableSchema * execute(const QString &tableName, ExecutionArguments *args)
Definition KDbAlter.cpp:813
KDbAlterTableHandler & operator<<(ActionBase *action)
Definition KDbAlter.cpp:778
static int alteringTypeForProperty(const QByteArray &propertyName)
Definition KDbAlter.cpp:181
Provides database connection, allowing queries and data modification.
@ DropDestination
Drop destination table if exists.
bool storeMainFieldSchema(KDbField *field)
bool executeSql(const KDbEscapedString &sql)
Executes a new native (raw, backend-specific) SQL query.
virtual QString escapeIdentifier(const QString &id) const
Identifier escaping function in the associated KDbDriver.
bool createTable(KDbTableSchema *tableSchema, CreateTableOptions options=CreateTableOption::Default)
Creates a new table.
KDbDriver * driver() const
KDbTableSchema * tableSchema(int tableId)
bool storeExtendedTableSchemaData(KDbTableSchema *tableSchema)
bool isDatabaseUsed() const
@ DropDestination
Drop destination table if exists.
bool alterTableName(KDbTableSchema *tableSchema, const QString &newName, AlterTableNameOptions options=AlterTableNameOption::Default)
Alters name of table.
KDbConnectionOptions * options()
virtual KDbEscapedString valueToSql(KDbField::Type ftype, const QVariant &v) const
Specialized string for escaping.
virtual KDbField * field(int id)
int fieldCount() const
bool renameField(const QString &oldName, const QString &newName)
KDbField::List * fields()
Meta-data for a field.
Definition KDbField.h:72
QString name() const
Definition KDbField.cpp:256
bool isNotEmpty() const
Definition KDbField.h:307
bool isNotNull() const
Definition KDbField.h:302
Type type() const
Definition KDbField.cpp:379
QVariant defaultValue() const
Definition KDbField.cpp:281
bool removeField(KDbField *field) override
bool insertField(int index, KDbField *field) override
Autodeleting hash.
Definition KDbUtils.h:179
3-state logical type with three values: true, false and cancelled and convenient operators.
KDB_EXPORT bool isExtendedTableFieldProperty(const QByteArray &propertyName)
for isExtendedTableProperty()
Definition KDb.cpp:954
KDB_EXPORT bool setFieldProperties(KDbField *field, const QMap< QByteArray, QVariant > &values)
Definition KDb.cpp:833
KDB_EXPORT QString temporaryTableName(KDbConnection *conn, const QString &baseName)
Definition KDb.cpp:2055
KDB_EXPORT QVariant notEmptyValueForFieldType(KDbField::Type type)
Used in KDb::notEmptyValueForFieldType()
Definition KDb.cpp:1275
KDB_EXPORT bool setFieldProperty(KDbField *field, const QByteArray &propertyName, const QVariant &value)
Definition KDb.cpp:981
KDB_EXPORT QVariant emptyValueForFieldType(KDbField::Type type)
Used in KDb::emptyValueForFieldType()
Definition KDb.cpp:1222
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
T take(const Key &key)
T value(const Key &key) const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
void clear()
qsizetype count() const const
void removeAt(qsizetype i)
T value(qsizetype i) const const
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
iterator insert(const T &value)
bool isEmpty() const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QByteArray toLatin1() const const
CaseInsensitive
bool isNull() const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:59 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.