File indexing completed on 2024-10-06 12:46:08
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 #include "KDbAlter.h" 0021 #include "KDb.h" 0022 #include "KDbConnection.h" 0023 #include "KDbConnectionOptions.h" 0024 #include "kdb_debug.h" 0025 0026 #include <QMap> 0027 0028 #include <stdlib.h> 0029 0030 class Q_DECL_HIDDEN KDbAlterTableHandler::Private 0031 { 0032 public: 0033 Private() {} 0034 ~Private() { 0035 qDeleteAll(actions); 0036 } 0037 ActionList actions; 0038 //! @todo IMPORTANT: replace QPointer<KDbConnection> conn; 0039 KDbConnection* conn; 0040 private: 0041 Q_DISABLE_COPY(Private) 0042 }; 0043 0044 //! Define a global instance used to when returning null is needed 0045 #define DEFINE_NULL_OBJECT(name) \ 0046 class Null ## name : public KDbAlterTableHandler::name \ 0047 { \ 0048 public: \ 0049 Null ## name() : KDbAlterTableHandler::name(true) {} \ 0050 }; \ 0051 Null ## name null ## name 0052 0053 DEFINE_NULL_OBJECT(ChangeFieldPropertyAction); 0054 DEFINE_NULL_OBJECT(RemoveFieldAction); 0055 DEFINE_NULL_OBJECT(InsertFieldAction); 0056 DEFINE_NULL_OBJECT(MoveFieldPositionAction); 0057 0058 //-------------------------------------------------------- 0059 0060 KDbAlterTableHandler::ActionBase::ActionBase(bool null) 0061 : m_alteringRequirements(0) 0062 , m_order(-1) 0063 , m_null(null) 0064 { 0065 } 0066 0067 KDbAlterTableHandler::ActionBase::~ActionBase() 0068 { 0069 } 0070 0071 KDbAlterTableHandler::ChangeFieldPropertyAction& KDbAlterTableHandler::ActionBase::toChangeFieldPropertyAction() 0072 { 0073 if (dynamic_cast<ChangeFieldPropertyAction*>(this)) 0074 return *dynamic_cast<ChangeFieldPropertyAction*>(this); 0075 return nullChangeFieldPropertyAction; 0076 } 0077 0078 KDbAlterTableHandler::RemoveFieldAction& KDbAlterTableHandler::ActionBase::toRemoveFieldAction() 0079 { 0080 if (dynamic_cast<RemoveFieldAction*>(this)) 0081 return *dynamic_cast<RemoveFieldAction*>(this); 0082 return nullRemoveFieldAction; 0083 } 0084 0085 KDbAlterTableHandler::InsertFieldAction& KDbAlterTableHandler::ActionBase::toInsertFieldAction() 0086 { 0087 if (dynamic_cast<InsertFieldAction*>(this)) 0088 return *dynamic_cast<InsertFieldAction*>(this); 0089 return nullInsertFieldAction; 0090 } 0091 0092 KDbAlterTableHandler::MoveFieldPositionAction& KDbAlterTableHandler::ActionBase::toMoveFieldPositionAction() 0093 { 0094 if (dynamic_cast<MoveFieldPositionAction*>(this)) 0095 return *dynamic_cast<MoveFieldPositionAction*>(this); 0096 return nullMoveFieldPositionAction; 0097 } 0098 0099 void KDbAlterTableHandler::ActionBase::debug(const DebugOptions& debugOptions) 0100 { 0101 kdbDebug() << debugString(debugOptions) 0102 << " (req = " << alteringRequirements() << ")"; 0103 } 0104 0105 //-------------------------------------------------------- 0106 0107 KDbAlterTableHandler::FieldActionBase::FieldActionBase(const QString& fieldName, int uid) 0108 : ActionBase(false) 0109 , m_fieldUID(uid) 0110 , m_fieldName(fieldName) 0111 { 0112 } 0113 0114 KDbAlterTableHandler::FieldActionBase::FieldActionBase(bool) 0115 : ActionBase(true) 0116 , m_fieldUID(-1) 0117 { 0118 } 0119 0120 KDbAlterTableHandler::FieldActionBase::~FieldActionBase() 0121 { 0122 } 0123 0124 //-------------------------------------------------------- 0125 0126 //! @internal 0127 struct KDb_AlterTableHandlerStatic { 0128 KDb_AlterTableHandlerStatic() { 0129 #define I(name, type) \ 0130 types.insert(QByteArray(name).toLower(), int(KDbAlterTableHandler::type)) 0131 #define I2(name, type1, type2) \ 0132 flag = int(KDbAlterTableHandler::type1)|int(KDbAlterTableHandler::type2); \ 0133 if (flag & KDbAlterTableHandler::PhysicalAlteringRequired) \ 0134 flag |= KDbAlterTableHandler::MainSchemaAlteringRequired; \ 0135 types.insert(QByteArray(name).toLower(), flag) 0136 0137 /* useful links: 0138 https://dev.mysql.com/doc/refman/5.0/en/create-table.html 0139 */ 0140 // ExtendedSchemaAlteringRequired is here because when the field is renamed, 0141 // we need to do the same rename in extended table schema: <field name="..."> 0142 int flag; 0143 I2("name", PhysicalAlteringRequired, MainSchemaAlteringRequired); 0144 I2("type", PhysicalAlteringRequired, DataConversionRequired); 0145 I("caption", MainSchemaAlteringRequired); 0146 I("description", MainSchemaAlteringRequired); 0147 I2("unsigned", PhysicalAlteringRequired, DataConversionRequired); // always? 0148 I2("maxLength", PhysicalAlteringRequired, DataConversionRequired); // always? 0149 I2("precision", PhysicalAlteringRequired, DataConversionRequired); // always? 0150 I("defaultWidth", ExtendedSchemaAlteringRequired); 0151 // defaultValue: depends on backend, for mysql it can only by a constant or now()... 0152 // -- should we look at KDbDriver here? 0153 #ifdef KDB_UNFINISHED 0154 I2("defaultValue", PhysicalAlteringRequired, MainSchemaAlteringRequired); 0155 #else 0156 //! @todo reenable 0157 I("defaultValue", MainSchemaAlteringRequired); 0158 #endif 0159 I2("primaryKey", PhysicalAlteringRequired, DataConversionRequired); 0160 I2("unique", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here 0161 I2("notNull", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here 0162 // allowEmpty: only support it just at kexi level? maybe there is a backend that supports this? 0163 I2("allowEmpty", PhysicalAlteringRequired, MainSchemaAlteringRequired); 0164 I2("autoIncrement", PhysicalAlteringRequired, DataConversionRequired); // data conversion may be hard here 0165 I2("indexed", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here 0166 0167 // easier cases follow... 0168 I("visibleDecimalPlaces", ExtendedSchemaAlteringRequired); 0169 0170 //more to come... 0171 #undef I 0172 #undef I2 0173 } 0174 0175 QHash<QByteArray, int> types; 0176 }; 0177 0178 Q_GLOBAL_STATIC(KDb_AlterTableHandlerStatic, KDb_alteringTypeForProperty) 0179 0180 //! @internal 0181 int KDbAlterTableHandler::alteringTypeForProperty(const QByteArray& propertyName) 0182 { 0183 const int res = KDb_alteringTypeForProperty->types[propertyName.toLower()]; 0184 if (res == 0) { 0185 if (KDb::isExtendedTableFieldProperty(propertyName)) 0186 return int(ExtendedSchemaAlteringRequired); 0187 kdbWarning() << "property" << propertyName << "not found!"; 0188 } 0189 return res; 0190 } 0191 0192 //--- 0193 0194 KDbAlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction( 0195 const QString& fieldName, const QString& propertyName, const QVariant& newValue, int uid) 0196 : FieldActionBase(fieldName, uid) 0197 , m_propertyName(propertyName) 0198 , m_newValue(newValue) 0199 { 0200 } 0201 0202 KDbAlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction() 0203 : ChangeFieldPropertyAction(true) 0204 { 0205 } 0206 0207 KDbAlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction(bool null) 0208 : FieldActionBase(null) 0209 { 0210 } 0211 0212 KDbAlterTableHandler::ChangeFieldPropertyAction::~ChangeFieldPropertyAction() 0213 { 0214 } 0215 0216 void KDbAlterTableHandler::ChangeFieldPropertyAction::updateAlteringRequirements() 0217 { 0218 setAlteringRequirements(alteringTypeForProperty(m_propertyName.toLatin1())); 0219 } 0220 0221 QString KDbAlterTableHandler::ChangeFieldPropertyAction::debugString(const DebugOptions& debugOptions) 0222 { 0223 QString s = QString::fromLatin1("Set \"%1\" property for table field \"%2\" to \"%3\"") 0224 .arg(m_propertyName, fieldName(), m_newValue.toString()); 0225 if (debugOptions.showUID) { 0226 s.append(QString::fromLatin1(" (UID=%1)").arg(m_fieldUID)); 0227 } 0228 return s; 0229 } 0230 0231 static KDbAlterTableHandler::ActionDict* createActionDict( 0232 KDbAlterTableHandler::ActionDictDict *fieldActions, int forFieldUID) 0233 { 0234 KDbAlterTableHandler::ActionDict* dict = new KDbAlterTableHandler::ActionDict(); 0235 fieldActions->insert(forFieldUID, dict); 0236 return dict; 0237 } 0238 0239 static void debugAction(KDbAlterTableHandler::ActionBase *action, int nestingLevel, 0240 bool simulate, const QString& prependString = QString(), QString * debugTarget = nullptr) 0241 { 0242 QString debugString; 0243 if (!debugTarget) 0244 debugString = prependString; 0245 if (action) { 0246 KDbAlterTableHandler::ActionBase::DebugOptions debugOptions; 0247 debugOptions.showUID = debugTarget == nullptr; 0248 debugOptions.showFieldDebug = debugTarget != nullptr; 0249 debugString += action->debugString(debugOptions); 0250 } else { 0251 if (!debugTarget) { 0252 debugString += QLatin1String("[No action]"); //hmm 0253 } 0254 } 0255 if (debugTarget) { 0256 if (!debugString.isEmpty()) { 0257 *debugTarget += debugString + QLatin1Char('\n'); 0258 } 0259 } else { 0260 kdbDebug() << debugString; 0261 #ifdef KDB_DEBUG_GUI 0262 if (simulate) 0263 KDb::alterTableActionDebugGUI(debugString, nestingLevel); 0264 #else 0265 Q_UNUSED(simulate) 0266 Q_UNUSED(nestingLevel) 0267 #endif 0268 } 0269 } 0270 0271 static void debugActionDict(KDbAlterTableHandler::ActionDict *dict, int fieldUID, bool simulate) 0272 { 0273 QString fieldName; 0274 KDbAlterTableHandler::ActionDictConstIterator it(dict->constBegin()); 0275 if (it != dict->constEnd() && dynamic_cast<KDbAlterTableHandler::FieldActionBase*>(it.value())) { 0276 //retrieve field name from the 1st related action 0277 fieldName = dynamic_cast<KDbAlterTableHandler::FieldActionBase*>(it.value())->fieldName(); 0278 } 0279 else { 0280 fieldName = QLatin1String("??"); 0281 } 0282 QString dbg(QString::fromLatin1("Action dict for field \"%1\" (%2, UID=%3):") 0283 .arg(fieldName).arg(dict->count()).arg(fieldUID)); 0284 kdbDebug() << dbg; 0285 #ifdef KDB_DEBUG_GUI 0286 if (simulate) 0287 KDb::alterTableActionDebugGUI(dbg, 1); 0288 #endif 0289 for (;it != dict->constEnd(); ++it) { 0290 debugAction(it.value(), 2, simulate); 0291 } 0292 } 0293 0294 static void debugFieldActions(const KDbAlterTableHandler::ActionDictDict &fieldActions, bool simulate) 0295 { 0296 #ifdef KDB_DEBUG_GUI 0297 if (simulate) 0298 KDb::alterTableActionDebugGUI(QLatin1String("** Simplified Field Actions:")); 0299 #endif 0300 for (KDbAlterTableHandler::ActionDictDictConstIterator it(fieldActions.constBegin()); it != fieldActions.constEnd(); ++it) { 0301 debugActionDict(it.value(), it.key(), simulate); 0302 } 0303 } 0304 0305 /*! 0306 Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. 0307 Case 1. (special) 0308 when new action=[rename A to B] 0309 and exists=[rename B to C] 0310 => 0311 remove [rename B to C] 0312 and set result to new [rename A to C] 0313 and go to 1b. 0314 Case 1b. when new action=[rename A to B] 0315 and actions exist like [set property P to C in field B] 0316 or like [delete field B] 0317 or like [move field B] 0318 => 0319 change B to A for all these actions 0320 Case 2. when new action=[change property in field A] (property != name) 0321 and exists=[remove A] or exists=[change property in field A] 0322 => 0323 do not add [change property in field A] because it will be removed anyway or the property will change 0324 */ 0325 void KDbAlterTableHandler::ChangeFieldPropertyAction::simplifyActions(ActionDictDict *fieldActions) 0326 { 0327 ActionDict *actionsLikeThis = fieldActions->value(uid()); 0328 if (m_propertyName == QLatin1String("name")) { 0329 // Case 1. special: name1 -> name2, i.e. rename action 0330 QByteArray newName(newValue().toString().toLatin1()); 0331 // try to find rename(newName, otherName) action 0332 ActionBase *renameActionLikeThis = actionsLikeThis ? actionsLikeThis->value(newName) : nullptr; 0333 if (dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)) { 0334 // 1. instead of having rename(fieldName(), newValue()) action, 0335 // let's have rename(fieldName(), otherName) action 0336 m_newValue = dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue; 0337 /* KDbAlterTableHandler::ChangeFieldPropertyAction* newRenameAction 0338 = new KDbAlterTableHandler::ChangeFieldPropertyAction( *this ); 0339 newRenameAction->m_newValue = dynamic_cast<ChangeFieldPropertyAction*>(renameActionLikeThis)->m_newValue; 0340 // (m_order is the same as in newAction) 0341 // replace prev. rename action (if any) 0342 actionsLikeThis->remove( "name" ); 0343 ActionDict *adict = (*fieldActions)[ fieldName().toLatin1() ]; 0344 if (!adict) 0345 adict = createActionDict( fieldActions, fieldName() ); 0346 adict->insert(m_propertyName.toLatin1(), newRenameAction);*/ 0347 } else { 0348 ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->value(":remove:") : nullptr; 0349 if (removeActionForThisField) { 0350 //if this field is going to be removed, just change the action's field name 0351 // and do not add a new action 0352 } else { 0353 //just insert a copy of the rename action 0354 if (!actionsLikeThis) 0355 actionsLikeThis = createActionDict(fieldActions, uid()); 0356 KDbAlterTableHandler::ChangeFieldPropertyAction* newRenameAction 0357 = new KDbAlterTableHandler::ChangeFieldPropertyAction(*this); 0358 kdbDebug() << "insert into" << fieldName() << "dict:" << newRenameAction->debugString(); 0359 actionsLikeThis->insert(m_propertyName.toLatin1(), newRenameAction); 0360 return; 0361 } 0362 } 0363 if (actionsLikeThis) { 0364 // Case 1b. change "field name" information to fieldName() in any action that 0365 // is related to newName 0366 // e.g. if there is setCaption("B", "captionA") action after rename("A","B"), 0367 // replace setCaption action with setCaption("A", "captionA") 0368 foreach(ActionBase* action, *actionsLikeThis) { 0369 dynamic_cast<FieldActionBase*>(action)->setFieldName(fieldName()); 0370 } 0371 } 0372 return; 0373 } 0374 ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->value(":remove:") : nullptr; 0375 if (removeActionForThisField) { 0376 //if this field is going to be removed, do not add a new action 0377 return; 0378 } 0379 // Case 2. other cases: just give up with adding this "intermediate" action 0380 // so, e.g. [ setCaption(A, "captionA"), setCaption(A, "captionB") ] 0381 // becomes: [ setCaption(A, "captionB") ] 0382 // because adding this action does nothing 0383 ActionDict *nextActionsLikeThis = fieldActions->value(uid()); 0384 if (!nextActionsLikeThis || !nextActionsLikeThis->value(m_propertyName.toLatin1())) { 0385 //no such action, add this 0386 KDbAlterTableHandler::ChangeFieldPropertyAction* newAction 0387 = new KDbAlterTableHandler::ChangeFieldPropertyAction(*this); 0388 if (!nextActionsLikeThis) 0389 nextActionsLikeThis = createActionDict(fieldActions, uid()); 0390 nextActionsLikeThis->insert(m_propertyName.toLatin1(), newAction); 0391 } 0392 } 0393 0394 bool KDbAlterTableHandler::ChangeFieldPropertyAction::shouldBeRemoved(ActionDictDict *fieldActions) 0395 { 0396 Q_UNUSED(fieldActions); 0397 return 0 == fieldName().compare(m_newValue.toString(), Qt::CaseInsensitive); 0398 } 0399 0400 tristate KDbAlterTableHandler::ChangeFieldPropertyAction::updateTableSchema(KDbTableSchema* table, KDbField* field, 0401 QHash<QString, QString>* fieldHash) 0402 { 0403 //1. Simpler cases first: changes that do not affect table schema at all 0404 // "caption", "description", "defaultWidth", "visibleDecimalPlaces" 0405 if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.toLatin1())) { 0406 bool result = KDb::setFieldProperty(field, m_propertyName.toLatin1(), newValue()); 0407 return result; 0408 } 0409 0410 if (m_propertyName == QLatin1String("name")) { 0411 if (fieldHash->value(field->name()) == field->name()) 0412 fieldHash->remove(field->name()); 0413 fieldHash->insert(newValue().toString(), field->name()); 0414 (void)table->renameField(field, newValue().toString()); 0415 return true; 0416 } 0417 return cancelled; 0418 } 0419 0420 /*! Many of the properties must be applied using a separate algorithm. 0421 */ 0422 tristate KDbAlterTableHandler::ChangeFieldPropertyAction::execute(KDbConnection* conn, KDbTableSchema* table) 0423 { 0424 Q_UNUSED(conn); 0425 KDbField *field = table->field(fieldName()); 0426 if (!field) { 0427 //! @todo errmsg 0428 return false; 0429 } 0430 bool result; 0431 //1. Simpler cases first: changes that do not affect table schema at all 0432 // "caption", "description", "defaultWidth", "visibleDecimalPlaces" 0433 if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.toLatin1())) { 0434 result = KDb::setFieldProperty(field, m_propertyName.toLatin1(), newValue()); 0435 return result; 0436 } 0437 0438 //! @todo 0439 #if 1 0440 return true; 0441 #else 0442 //2. Harder cases, that often require special care 0443 if (m_propertyName == QLatin1String("name")) { 0444 /*mysql: 0445 A. Get real field type (it's safer): 0446 let <TYPE> be the 2nd "Type" column from result of "DESCRIBE tablename oldfieldname" 0447 ( https://dev.mysql.com/doc/refman/5.0/en/describe.html ) 0448 B. Run "ALTER TABLE tablename CHANGE oldfieldname newfieldname <TYPE>"; 0449 ( https://dev.mysql.com/doc/refman/5.0/en/alter-table.html ) 0450 */ 0451 } 0452 if (m_propertyName == QLatin1String("type")) { 0453 /*mysql: 0454 A. Like A. for "name" property above 0455 B. Construct <TYPE> string, eg. "varchar(50)" using the driver 0456 C. Like B. for "name" property above 0457 (mysql then truncate the values for changes like varchar -> integer, 0458 and properly convert the values for changes like integer -> varchar) 0459 */ 0460 //! @todo more cases to check 0461 } 0462 if (m_propertyName == QLatin1String("maxLength")) { 0463 //! @todo use "select max( length(o_name) ) from kexi__objects" 0464 0465 } 0466 if (m_propertyName == QLatin1String("primaryKey")) { 0467 //! @todo 0468 } 0469 0470 /* 0471 "name", "unsigned", "precision", 0472 "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty", 0473 "autoIncrement", "indexed", 0474 0475 0476 bool result = KDb::setFieldProperty(*field, m_propertyName.toLatin1(), newValue()); 0477 */ 0478 return result; 0479 #endif 0480 } 0481 0482 //-------------------------------------------------------- 0483 0484 KDbAlterTableHandler::RemoveFieldAction::RemoveFieldAction(const QString& fieldName, int uid) 0485 : FieldActionBase(fieldName, uid) 0486 { 0487 } 0488 0489 KDbAlterTableHandler::RemoveFieldAction::RemoveFieldAction(bool null) 0490 : FieldActionBase(null) 0491 { 0492 } 0493 0494 KDbAlterTableHandler::RemoveFieldAction::~RemoveFieldAction() 0495 { 0496 } 0497 0498 void KDbAlterTableHandler::RemoveFieldAction::updateAlteringRequirements() 0499 { 0500 //! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ? 0501 0502 setAlteringRequirements(PhysicalAlteringRequired); 0503 //! @todo 0504 } 0505 0506 QString KDbAlterTableHandler::RemoveFieldAction::debugString(const DebugOptions& debugOptions) 0507 { 0508 QString s = QString::fromLatin1("Delete table field \"%1\"").arg(fieldName()); 0509 if (debugOptions.showUID) { 0510 s.append(QString::fromLatin1(" (UID=%1)").arg(uid())); 0511 } 0512 return s; 0513 } 0514 0515 /*! 0516 Legend: A,B==objects, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. 0517 Preconditions: we assume there cannot be such case encountered: ([remove A], [do something related on A]) 0518 (except for [remove A], [insert A]) 0519 General Case: it's safe to always insert a [remove A] action. 0520 */ 0521 void KDbAlterTableHandler::RemoveFieldAction::simplifyActions(ActionDictDict *fieldActions) 0522 { 0523 //! @todo not checked 0524 KDbAlterTableHandler::RemoveFieldAction* newAction 0525 = new KDbAlterTableHandler::RemoveFieldAction(*this); 0526 ActionDict *actionsLikeThis = fieldActions->value(uid()); 0527 if (!actionsLikeThis) 0528 actionsLikeThis = createActionDict(fieldActions, uid()); 0529 actionsLikeThis->insert(":remove:", newAction); //special 0530 } 0531 0532 tristate KDbAlterTableHandler::RemoveFieldAction::updateTableSchema(KDbTableSchema* table, KDbField* field, 0533 QHash<QString, QString>* fieldHash) 0534 { 0535 fieldHash->remove(field->name()); 0536 table->removeField(field); 0537 return true; 0538 } 0539 0540 tristate KDbAlterTableHandler::RemoveFieldAction::execute(KDbConnection* conn, KDbTableSchema* table) 0541 { 0542 Q_UNUSED(conn); 0543 Q_UNUSED(table); 0544 //! @todo 0545 return true; 0546 } 0547 0548 //-------------------------------------------------------- 0549 0550 KDbAlterTableHandler::InsertFieldAction::InsertFieldAction(int fieldIndex, KDbField *field, int uid) 0551 : FieldActionBase(field->name(), uid) 0552 , m_index(fieldIndex) 0553 , m_field(nullptr) 0554 { 0555 Q_ASSERT(field); 0556 setField(field); 0557 } 0558 0559 KDbAlterTableHandler::InsertFieldAction::InsertFieldAction(const InsertFieldAction& action) 0560 : FieldActionBase(action) //action.fieldName(), action.uid()) 0561 , m_index(action.index()) 0562 { 0563 m_field = new KDbField(*action.field()); 0564 } 0565 0566 KDbAlterTableHandler::InsertFieldAction::InsertFieldAction() 0567 : InsertFieldAction(true) 0568 { 0569 } 0570 0571 KDbAlterTableHandler::InsertFieldAction::InsertFieldAction(bool null) 0572 : FieldActionBase(null) 0573 , m_index(0) 0574 , m_field(nullptr) 0575 { 0576 } 0577 0578 KDbAlterTableHandler::InsertFieldAction::~InsertFieldAction() 0579 { 0580 delete m_field; 0581 } 0582 0583 void KDbAlterTableHandler::InsertFieldAction::setField(KDbField* field) 0584 { 0585 if (m_field) 0586 delete m_field; 0587 m_field = field; 0588 setFieldName(m_field ? m_field->name() : QString()); 0589 } 0590 0591 void KDbAlterTableHandler::InsertFieldAction::updateAlteringRequirements() 0592 { 0593 //! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ? 0594 0595 setAlteringRequirements(PhysicalAlteringRequired); 0596 //! @todo 0597 } 0598 0599 QString KDbAlterTableHandler::InsertFieldAction::debugString(const DebugOptions& debugOptions) 0600 { 0601 QString s = QString::fromLatin1("Insert table field \"%1\" at position %2") 0602 .arg(m_field->name()).arg(m_index); 0603 if (debugOptions.showUID) { 0604 s.append(QString::fromLatin1(" (UID=%1)").arg(m_fieldUID)); 0605 } 0606 if (debugOptions.showFieldDebug) { 0607 s.append(QString::fromLatin1(" (%1)").arg(KDbUtils::debugString<KDbField>(*m_field))); 0608 } 0609 return s; 0610 } 0611 0612 /*! 0613 Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. 0614 0615 0616 Case 1: there are "change property" actions after the Insert action. 0617 -> change the properties in the Insert action itself and remove the "change property" actions. 0618 Examples: 0619 [Insert A] && [rename A to B] => [Insert B] 0620 [Insert A] && [change property P in field A] => [Insert A with P altered] 0621 Comment: we need to do this reduction because otherwise we'd need to do psyhical altering 0622 right after [Insert A] if [rename A to B] follows. 0623 */ 0624 void KDbAlterTableHandler::InsertFieldAction::simplifyActions(ActionDictDict *fieldActions) 0625 { 0626 // Try to find actions related to this action 0627 ActionDict *actionsForThisField = fieldActions->value(uid()); 0628 0629 ActionBase *removeActionForThisField = actionsForThisField ? actionsForThisField->value(":remove:") : nullptr; 0630 if (removeActionForThisField) { 0631 //if this field is going to be removed, do not add a new action 0632 //and remove the "Remove" action 0633 actionsForThisField->remove(":remove:"); 0634 return; 0635 } 0636 if (actionsForThisField) { 0637 //collect property values that have to be changed in this field 0638 QMap<QByteArray, QVariant> values; 0639 ActionDict *newActionsForThisField = new ActionDict(); // this will replace actionsForThisField after the loop 0640 QSet<ActionBase*> actionsToDelete; // used to collect actions taht we soon delete but cannot delete in the loop below 0641 for (ActionDictConstIterator it(actionsForThisField->constBegin()); it != actionsForThisField->constEnd();++it) { 0642 ChangeFieldPropertyAction* changePropertyAction = dynamic_cast<ChangeFieldPropertyAction*>(it.value()); 0643 if (changePropertyAction) { 0644 //if this field is going to be renamed, also update fieldName() 0645 if (changePropertyAction->propertyName() == QLatin1String("name")) { 0646 setFieldName(changePropertyAction->newValue().toString()); 0647 } 0648 values.insert(changePropertyAction->propertyName().toLatin1(), changePropertyAction->newValue()); 0649 //the subsequent "change property" action is no longer needed 0650 actionsToDelete.insert(it.value()); 0651 } else { 0652 //keep 0653 newActionsForThisField->insert(it.key(), it.value()); 0654 } 0655 } 0656 qDeleteAll(actionsToDelete); 0657 actionsForThisField->setAutoDelete(false); 0658 delete actionsForThisField; 0659 actionsForThisField = newActionsForThisField; 0660 fieldActions->take(uid()); 0661 fieldActions->insert(uid(), actionsForThisField); 0662 if (!values.isEmpty()) { 0663 //update field, so it will be created as one step 0664 KDbField *f = new KDbField(*field()); 0665 if (KDb::setFieldProperties(f, values)) { 0666 setField(f); 0667 kdbDebug() << field(); 0668 #ifdef KDB_DEBUG_GUI 0669 KDb::alterTableActionDebugGUI( 0670 QLatin1String("** Property-set actions moved to field definition itself:\n") 0671 + KDbUtils::debugString<KDbField>(*field()), 0); 0672 #endif 0673 } else { 0674 #ifdef KDB_DEBUG_GUI 0675 KDb::alterTableActionDebugGUI( 0676 QLatin1String("** Failed to set properties for field ") + KDbUtils::debugString<KDbField>(*field()), 0); 0677 #endif 0678 kdbWarning() << "setFieldProperties() failed!"; 0679 delete f; 0680 } 0681 } 0682 } 0683 //ok, insert this action 0684 //! @todo not checked 0685 KDbAlterTableHandler::InsertFieldAction* newAction 0686 = new KDbAlterTableHandler::InsertFieldAction(*this); 0687 if (!actionsForThisField) 0688 actionsForThisField = createActionDict(fieldActions, uid()); 0689 actionsForThisField->insert(":insert:", newAction); //special 0690 } 0691 0692 tristate KDbAlterTableHandler::InsertFieldAction::updateTableSchema(KDbTableSchema* table, KDbField* field, 0693 QHash<QString, QString>* fieldMap) 0694 { 0695 //in most cases we won't add the field to fieldMap 0696 Q_UNUSED(field); 0697 //! @todo add it only when there should be fixed value (e.g. default) set for this new field... 0698 fieldMap->remove(this->field()->name()); 0699 table->insertField(index(), new KDbField(*this->field())); 0700 return true; 0701 } 0702 0703 tristate KDbAlterTableHandler::InsertFieldAction::execute(KDbConnection* conn, KDbTableSchema* table) 0704 { 0705 Q_UNUSED(conn); 0706 Q_UNUSED(table); 0707 //! @todo 0708 return true; 0709 } 0710 0711 //-------------------------------------------------------- 0712 0713 KDbAlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction( 0714 int fieldIndex, const QString& fieldName, int uid) 0715 : FieldActionBase(fieldName, uid) 0716 , m_index(fieldIndex) 0717 { 0718 } 0719 0720 KDbAlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(bool null) 0721 : FieldActionBase(null) 0722 , m_index(-1) 0723 { 0724 } 0725 0726 KDbAlterTableHandler::MoveFieldPositionAction::~MoveFieldPositionAction() 0727 { 0728 } 0729 0730 void KDbAlterTableHandler::MoveFieldPositionAction::updateAlteringRequirements() 0731 { 0732 setAlteringRequirements(MainSchemaAlteringRequired); 0733 //! @todo 0734 } 0735 0736 QString KDbAlterTableHandler::MoveFieldPositionAction::debugString(const DebugOptions& debugOptions) 0737 { 0738 QString s = QString::fromLatin1("Move table field \"%1\" to position %2") 0739 .arg(fieldName()).arg(m_index); 0740 if (debugOptions.showUID) { 0741 s.append(QString::fromLatin1(" (UID=%1)").arg(uid())); 0742 } 0743 return s; 0744 } 0745 0746 void KDbAlterTableHandler::MoveFieldPositionAction::simplifyActions(ActionDictDict *fieldActions) 0747 { 0748 Q_UNUSED(fieldActions); 0749 //! @todo 0750 } 0751 0752 tristate KDbAlterTableHandler::MoveFieldPositionAction::execute(KDbConnection* conn, KDbTableSchema* table) 0753 { 0754 Q_UNUSED(conn); 0755 Q_UNUSED(table); 0756 //! @todo 0757 return true; 0758 } 0759 0760 //-------------------------------------------------------- 0761 0762 KDbAlterTableHandler::KDbAlterTableHandler(KDbConnection* conn) 0763 : d(new Private()) 0764 { 0765 d->conn = conn; 0766 } 0767 0768 KDbAlterTableHandler::~KDbAlterTableHandler() 0769 { 0770 delete d; 0771 } 0772 0773 void KDbAlterTableHandler::addAction(ActionBase* action) 0774 { 0775 d->actions.append(action); 0776 } 0777 0778 KDbAlterTableHandler& KDbAlterTableHandler::operator<< (ActionBase* action) 0779 { 0780 d->actions.append(action); 0781 return *this; 0782 } 0783 0784 const KDbAlterTableHandler::ActionList& KDbAlterTableHandler::actions() const 0785 { 0786 return d->actions; 0787 } 0788 0789 void KDbAlterTableHandler::removeAction(int index) 0790 { 0791 d->actions.removeAt(index); 0792 } 0793 0794 void KDbAlterTableHandler::clear() 0795 { 0796 d->actions.clear(); 0797 } 0798 0799 void KDbAlterTableHandler::setActions(const ActionList& actions) 0800 { 0801 qDeleteAll(d->actions); 0802 d->actions = actions; 0803 } 0804 0805 void KDbAlterTableHandler::debug() 0806 { 0807 kdbDebug() << "KDbAlterTableHandler's actions:"; 0808 foreach(ActionBase* action, d->actions) { 0809 action->debug(); 0810 } 0811 } 0812 0813 KDbTableSchema* KDbAlterTableHandler::execute(const QString& tableName, ExecutionArguments* args) 0814 { 0815 args->result = false; 0816 if (!d->conn) { 0817 //! @todo err msg? 0818 return nullptr; 0819 } 0820 if (d->conn->options()->isReadOnly()) { 0821 //! @todo err msg? 0822 return nullptr; 0823 } 0824 if (!d->conn->isDatabaseUsed()) { 0825 //! @todo err msg? 0826 return nullptr; 0827 } 0828 KDbTableSchema *oldTable = d->conn->tableSchema(tableName); 0829 if (!oldTable) { 0830 //! @todo err msg? 0831 return nullptr; 0832 } 0833 0834 if (!args->debugString) 0835 debug(); 0836 0837 // Find a sum of requirements... 0838 int allActionsCount = 0; 0839 foreach(ActionBase* action, d->actions) { 0840 action->updateAlteringRequirements(); 0841 action->m_order = allActionsCount++; 0842 } 0843 0844 /* Simplify actions list if possible and check for errors 0845 0846 How to do it? 0847 - track property changes/deletions in reversed order 0848 - reduce intermediate actions 0849 0850 Trivial example 1: 0851 *action1: "rename field a to b" 0852 *action2: "rename field b to c" 0853 *action3: "rename field c to d" 0854 0855 After reduction: 0856 *action1: "rename field a to d" 0857 Summing up: we have tracked what happens to field curently named "d" 0858 and eventually discovered that it was originally named "a". 0859 0860 Trivial example 2: 0861 *action1: "rename field a to b" 0862 *action2: "rename field b to c" 0863 *action3: "remove field b" 0864 After reduction: 0865 *action3: "remove field b" 0866 Summing up: we have noticed that field "b" has beed eventually removed 0867 so we needed to find all actions related to this field and remove them. 0868 This is good optimization, as some of the eventually removed actions would 0869 be difficult to perform and/or costly, what would be a waste of resources 0870 and a source of unwanted questions sent to the user. 0871 */ 0872 0873 0874 0875 // Fields-related actions. 0876 ActionDictDict fieldActions; 0877 ActionBase* action; 0878 for (int i = d->actions.count() - 1; i >= 0; i--) { 0879 d->actions[i]->simplifyActions(&fieldActions); 0880 } 0881 0882 if (!args->debugString) 0883 debugFieldActions(fieldActions, args->simulate); 0884 0885 // Prepare actions for execution ---- 0886 // - Sort actions by order 0887 ActionsVector actionsVector(allActionsCount); 0888 int currentActionsCount = 0; //some actions may be removed 0889 args->requirements = 0; 0890 QSet<QString> fieldsWithChangedMainSchema; // Used to collect fields with changed main schema. 0891 // This will be used when recreateTable is false to update kexi__fields 0892 for (ActionDictDictConstIterator it(fieldActions.constBegin()); it != fieldActions.constEnd(); ++it) { 0893 for (KDbAlterTableHandler::ActionDictConstIterator it2(it.value()->constBegin()); 0894 it2 != it.value()->constEnd(); ++it2, currentActionsCount++) 0895 { 0896 if (it2.value()->shouldBeRemoved(&fieldActions)) 0897 continue; 0898 actionsVector[ it2.value()->m_order ] = it2.value(); 0899 // a sum of requirements... 0900 const int r = it2.value()->alteringRequirements(); 0901 args->requirements |= r; 0902 if (r & MainSchemaAlteringRequired && dynamic_cast<ChangeFieldPropertyAction*>(it2.value())) { 0903 // Remember, this will be used when recreateTable is false to update kexi__fields, below. 0904 fieldsWithChangedMainSchema.insert( 0905 dynamic_cast<ChangeFieldPropertyAction*>(it2.value())->fieldName()); 0906 } 0907 } 0908 } 0909 // - Debug 0910 QString dbg = QString::fromLatin1("** Overall altering requirements: %1").arg(args->requirements); 0911 kdbDebug() << dbg; 0912 0913 if (args->onlyComputeRequirements) { 0914 args->result = true; 0915 return nullptr; 0916 } 0917 0918 const bool recreateTable = (args->requirements & PhysicalAlteringRequired); 0919 0920 #ifdef KDB_DEBUG_GUI 0921 if (args->simulate) 0922 KDb::alterTableActionDebugGUI(dbg, 0); 0923 #endif 0924 dbg = QString::fromLatin1("** Ordered, simplified actions (%1, was %2):") 0925 .arg(currentActionsCount).arg(allActionsCount); 0926 kdbDebug() << dbg; 0927 #ifdef KDB_DEBUG_GUI 0928 if (args->simulate) 0929 KDb::alterTableActionDebugGUI(dbg, 0); 0930 #endif 0931 for (int i = 0; i < allActionsCount; i++) { 0932 debugAction(actionsVector.at(i), 1, args->simulate, 0933 QString::fromLatin1("%1: ").arg(i + 1), args->debugString); 0934 } 0935 0936 if (args->requirements == 0) {//nothing to do 0937 args->result = true; 0938 return oldTable; 0939 } 0940 if (args->simulate) {//do not execute 0941 args->result = true; 0942 return oldTable; 0943 } 0944 //! @todo transaction! 0945 0946 // Create a new KDbTableSchema 0947 KDbTableSchema *newTable = recreateTable ? new KDbTableSchema(*oldTable, false/*!copy id*/) : oldTable; 0948 // find nonexisting temp name for new table schema 0949 if (recreateTable) { 0950 QString tempDestTableName = KDb::temporaryTableName(d->conn, newTable->name()); 0951 newTable->setName(tempDestTableName); 0952 } 0953 kdbDebug() << *oldTable; 0954 if (recreateTable && !args->debugString) { 0955 kdbDebug() << *newTable; 0956 } 0957 0958 // Update table schema in memory ---- 0959 int lastUID = -1; 0960 KDbField *currentField = nullptr; 0961 QHash<QString, QString> fieldHash; // a map from new value to old value 0962 foreach(KDbField* f, *newTable->fields()) { 0963 fieldHash.insert(f->name(), f->name()); 0964 } 0965 for (int i = 0; i < allActionsCount; i++) { 0966 action = actionsVector.at(i); 0967 if (!action) 0968 continue; 0969 //remember the current KDbField object because soon we may be unable to find it by name: 0970 FieldActionBase *fieldAction = dynamic_cast<FieldActionBase*>(action); 0971 if (!fieldAction) { 0972 currentField = nullptr; 0973 } else { 0974 if (lastUID != fieldAction->uid()) { 0975 currentField = newTable->field(fieldAction->fieldName()); 0976 lastUID = currentField ? fieldAction->uid() : -1; 0977 } 0978 InsertFieldAction *insertFieldAction = dynamic_cast<InsertFieldAction*>(action); 0979 if (insertFieldAction && insertFieldAction->index() > newTable->fieldCount()) { 0980 //update index: there can be empty rows 0981 insertFieldAction->setIndex(newTable->fieldCount()); 0982 } 0983 } 0984 args->result = action->updateTableSchema(newTable, currentField, &fieldHash); 0985 if (args->result != true) { 0986 if (recreateTable) 0987 delete newTable; 0988 return nullptr; 0989 } 0990 } 0991 0992 if (recreateTable) { 0993 // Create the destination table with temporary name 0994 if (!d->conn->createTable(newTable, 0995 KDbConnection::CreateTableOptions(KDbConnection::CreateTableOption::Default) 0996 & ~KDbConnection::CreateTableOptions(KDbConnection::CreateTableOption::DropDestination))) 0997 { 0998 m_result = d->conn->result(); 0999 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 */