1 /*************************************************************************** 2 mymoneystoragesql.cpp 3 --------------------- 4 begin : 11 November 2005 5 copyright : (C) 2005 by Tony Bloomfield 6 email : tonybloom@users.sourceforge.net 7 : Fernando Vilas <fvilas@iname.com> 8 : Christian Dávid <christian-david@web.de> 9 (C) 2017 by Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 10 ***************************************************************************/ 11 12 /*************************************************************************** 13 * * 14 * This program is free software; you can redistribute it and/or modify * 15 * it under the terms of the GNU General Public License as published by * 16 * the Free Software Foundation; either version 2 of the License, or * 17 * (at your option) any later version. * 18 * * 19 ***************************************************************************/ 20 21 #ifndef MYMONEYSTORAGESQL_P_H 22 #define MYMONEYSTORAGESQL_P_H 23 24 #include "mymoneystoragesql.h" 25 26 // ---------------------------------------------------------------------------- 27 // System Includes 28 #include <algorithm> 29 30 // ---------------------------------------------------------------------------- 31 // QT Includes 32 33 #include <QString> 34 #include <QDateTime> 35 #include <QStringList> 36 #include <QIODevice> 37 #include <QUrlQuery> 38 #include <QSqlQuery> 39 #include <QSqlError> 40 #include <QList> 41 #include <QSqlRecord> 42 #include <QMap> 43 #include <QFile> 44 #include <QVariant> 45 #include <QColor> 46 #include <QDebug> 47 #include <QStack> 48 49 // ---------------------------------------------------------------------------- 50 // KDE Includes 51 52 #include <KLocalizedString> 53 54 // ---------------------------------------------------------------------------- 55 // Project Includes 56 57 #include "mymoneystoragemgr.h" 58 #include "onlinejobadministration.h" 59 #include "onlinetasks/interfaces/tasks/onlinetask.h" 60 #include "mymoneycostcenter.h" 61 #include "mymoneyexception.h" 62 #include "mymoneyinstitution.h" 63 #include "mymoneyaccount.h" 64 #include "mymoneysecurity.h" 65 #include "mymoneymoney.h" 66 #include "mymoneyschedule.h" 67 #include "mymoneypayee.h" 68 #include "mymoneytag.h" 69 #include "mymoneysplit.h" 70 #include "mymoneytransaction.h" 71 #include "mymoneytransactionfilter.h" 72 #include "mymoneybudget.h" 73 #include "mymoneyreport.h" 74 #include "mymoneyprice.h" 75 #include "mymoneyutils.h" 76 #include "mymoneydbdef.h" 77 #include "mymoneydbdriver.h" 78 #include "payeeidentifierdata.h" 79 #include "payeeidentifier.h" 80 #include "payeeidentifiertyped.h" 81 #include "payeeidentifier/ibanbic/ibanbic.h" 82 #include "payeeidentifier/nationalaccount/nationalaccount.h" 83 #include "onlinetasks/sepa/sepaonlinetransferimpl.h" 84 #include "xmlstoragehelper.h" 85 #include "mymoneyenums.h" 86 87 using namespace eMyMoney; 88 89 class FilterFail 90 { 91 public: FilterFail(const MyMoneyTransactionFilter & filter)92 explicit FilterFail(const MyMoneyTransactionFilter& filter) : m_filter(filter) {} 93 operator()94 inline bool operator()(const QPair<QString, MyMoneyTransaction>& transactionPair) { 95 return (*this)(transactionPair.second); 96 } 97 operator()98 inline bool operator()(const MyMoneyTransaction& transaction) { 99 return !m_filter.match(transaction); 100 } 101 102 private: 103 MyMoneyTransactionFilter m_filter; 104 }; 105 106 //***************************************************************************** 107 // Create a class to handle db transactions using scope 108 // 109 // Don't let the database object get destroyed while this object exists, 110 // that would result in undefined behavior. 111 class MyMoneyDbTransaction 112 { 113 public: MyMoneyDbTransaction(MyMoneyStorageSql & db,const QString & name)114 explicit MyMoneyDbTransaction(MyMoneyStorageSql& db, const QString& name) : 115 m_db(db), m_name(name) 116 { 117 db.startCommitUnit(name); 118 } 119 ~MyMoneyDbTransaction()120 ~MyMoneyDbTransaction() 121 { 122 if (std::uncaught_exception()) { 123 m_db.cancelCommitUnit(m_name); 124 } else { 125 try{ 126 m_db.endCommitUnit(m_name); 127 } catch (const MyMoneyException &) { 128 try { 129 m_db.cancelCommitUnit(m_name); 130 } catch (const MyMoneyException &e) { 131 qDebug() << e.what(); 132 } 133 } 134 } 135 } 136 private: 137 MyMoneyStorageSql& m_db; 138 QString m_name; 139 }; 140 141 /** 142 * The MyMoneySqlQuery class is derived from QSqlQuery to provide 143 * a way to adjust some queries based on database type and make 144 * debugging easier by providing a place to put debug statements. 145 */ 146 class MyMoneySqlQuery : public QSqlQuery 147 { 148 public: 149 explicit MyMoneySqlQuery(MyMoneyStorageSql* db = 0) : 150 QSqlQuery(*db) 151 { 152 } 153 ~MyMoneySqlQuery()154 virtual ~MyMoneySqlQuery() 155 { 156 } 157 exec()158 bool exec() 159 { 160 qDebug() << "start sql:" << lastQuery(); 161 bool rc = QSqlQuery::exec(); 162 qDebug() << "end sql:" << QSqlQuery::executedQuery(); 163 qDebug() << "***Query returned:" << rc << ", row count:" << numRowsAffected(); 164 return (rc); 165 } 166 exec(const QString & query)167 bool exec(const QString & query) 168 { 169 qDebug() << "start sql:" << query; 170 bool rc = QSqlQuery::exec(query); 171 qDebug() << "end sql:" << QSqlQuery::executedQuery(); 172 qDebug() << "***Query returned:" << rc << ", row count:" << numRowsAffected(); 173 return rc; 174 } 175 prepare(const QString & query)176 bool prepare(const QString & query) 177 { 178 return (QSqlQuery::prepare(query)); 179 } 180 }; 181 182 #define GETSTRING(a) query.value(a).toString() 183 #define GETDATE(a) getDate(GETSTRING(a)) 184 #define GETDATE_D(a) d->getDate(GETSTRING(a)) 185 #define GETDATETIME(a) getDateTime(GETSTRING(a)) 186 #define GETINT(a) query.value(a).toInt() 187 #define GETULL(a) query.value(a).toULongLong() 188 #define MYMONEYEXCEPTIONSQL(exceptionMessage) MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, exceptionMessage)) 189 #define MYMONEYEXCEPTIONSQL_D(exceptionMessage) MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, exceptionMessage)) 190 191 class MyMoneyStorageSqlPrivate 192 { 193 Q_DISABLE_COPY(MyMoneyStorageSqlPrivate) Q_DECLARE_PUBLIC(MyMoneyStorageSql)194 Q_DECLARE_PUBLIC(MyMoneyStorageSql) 195 196 public: 197 explicit MyMoneyStorageSqlPrivate(MyMoneyStorageSql* qq) : 198 q_ptr(qq), 199 m_dbVersion(0), 200 m_storage(nullptr), 201 m_loadAll(false), 202 m_override(false), 203 m_institutions(0), 204 m_accounts(0), 205 m_payees(0), 206 m_tags(0), 207 m_transactions(0), 208 m_splits(0), 209 m_securities(0), 210 m_prices(0), 211 m_currencies(0), 212 m_schedules(0), 213 m_reports(0), 214 m_kvps(0), 215 m_budgets(0), 216 m_onlineJobs(0), 217 m_payeeIdentifier(0), 218 m_hiIdInstitutions(0), 219 m_hiIdPayees(0), 220 m_hiIdTags(0), 221 m_hiIdAccounts(0), 222 m_hiIdTransactions(0), 223 m_hiIdSchedules(0), 224 m_hiIdSecurities(0), 225 m_hiIdReports(0), 226 m_hiIdBudgets(0), 227 m_hiIdOnlineJobs(0), 228 m_hiIdPayeeIdentifier(0), 229 m_hiIdCostCenter(0), 230 m_displayStatus(false), 231 m_readingPrices(false), 232 m_newDatabase(false), 233 m_progressCallback(nullptr) 234 { 235 m_preferred.setReportAllSplits(false); 236 } 237 ~MyMoneyStorageSqlPrivate()238 ~MyMoneyStorageSqlPrivate() 239 { 240 } 241 242 enum class SQLAction { 243 Save, 244 Modify, 245 Remove 246 }; 247 248 /** 249 * MyMoneyStorageSql get highest ID number from the database 250 * 251 * @return : highest ID number 252 */ highestNumberFromIdString(QString tableName,QString tableField,int prefixLength)253 ulong highestNumberFromIdString(QString tableName, QString tableField, int prefixLength) 254 { 255 Q_Q(MyMoneyStorageSql); 256 MyMoneyDbTransaction t(*q, Q_FUNC_INFO); 257 QSqlQuery query(*q); 258 259 if (!query.exec(m_driver->highestNumberFromIdString(tableName, tableField, prefixLength)) || !query.next()) 260 throw MYMONEYEXCEPTIONSQL("retrieving highest ID number"); 261 262 return query.value(0).toULongLong(); 263 } 264 265 /** 266 * @name writeFromStorageMethods 267 * @{ 268 * These method write all data from m_storage to the database. Data which is 269 * stored in the database is deleted. 270 */ 271 void writeUserInformation(); 272 writeInstitutions()273 void writeInstitutions() 274 { 275 Q_Q(MyMoneyStorageSql); 276 // first, get a list of what's on the database 277 // anything not in the list needs to be inserted 278 // anything which is will be updated and removed from the list 279 // anything left over at the end will need to be deleted 280 // this is an expensive and inconvenient way to do things; find a better way 281 // one way would be to build the lists when reading the db 282 // unfortunately this object does not persist between read and write 283 // it would also be nice if we could tell which objects had been updated since we read them in 284 QList<QString> dbList; 285 QSqlQuery query(*q); 286 query.prepare("SELECT id FROM kmmInstitutions;"); 287 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Institution list"); // krazy:exclude=crashy 288 while (query.next()) dbList.append(query.value(0).toString()); 289 290 const QList<MyMoneyInstitution> list = m_storage->institutionList(); 291 QList<MyMoneyInstitution> insertList; 292 QList<MyMoneyInstitution> updateList; 293 QSqlQuery query2(*q); 294 query.prepare(m_db.m_tables["kmmInstitutions"].updateString()); 295 query2.prepare(m_db.m_tables["kmmInstitutions"].insertString()); 296 signalProgress(0, list.count(), "Writing Institutions..."); 297 foreach (const MyMoneyInstitution& i, list) { 298 if (dbList.contains(i.id())) { 299 dbList.removeAll(i.id()); 300 updateList << i; 301 } else { 302 insertList << i; 303 } 304 signalProgress(++m_institutions, 0); 305 } 306 if (!insertList.isEmpty()) 307 writeInstitutionList(insertList, query2); 308 309 if (!updateList.isEmpty()) 310 writeInstitutionList(updateList, query); 311 312 if (!dbList.isEmpty()) { 313 QVariantList deleteList; 314 // qCopy segfaults here, so do it with a hand-rolled loop 315 foreach (const QString& it, dbList) { 316 deleteList << it; 317 } 318 query.prepare("DELETE FROM kmmInstitutions WHERE id = :id"); 319 query.bindValue(":id", deleteList); 320 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Institution"); 321 322 deleteKeyValuePairs("INSTITUTION", deleteList); 323 // delete the old version in any case 324 deleteKeyValuePairs("OFXSETTINGS", deleteList); 325 } 326 } 327 writePayees()328 void writePayees() 329 { 330 Q_Q(MyMoneyStorageSql); 331 // first, get a list of what's on the database (see writeInstitutions) 332 333 QSqlQuery query(*q); 334 query.prepare("SELECT id FROM kmmPayees;"); 335 if (!query.exec()) 336 throw MYMONEYEXCEPTIONSQL("building Payee list"); // krazy:exclude=crashy 337 338 QList<QString> dbList; 339 dbList.reserve(query.numRowsAffected()); 340 while (query.next()) 341 dbList.append(query.value(0).toString()); 342 343 QList<MyMoneyPayee> list = m_storage->payeeList(); 344 MyMoneyPayee user(QString("USER"), m_storage->user()); 345 list.prepend(user); 346 signalProgress(0, list.count(), "Writing Payees..."); 347 348 Q_FOREACH(const MyMoneyPayee& it, list) { 349 if (dbList.contains(it.id())) { 350 dbList.removeAll(it.id()); 351 q->modifyPayee(it); 352 } else { 353 q->addPayee(it); 354 } 355 signalProgress(++m_payees, 0); 356 } 357 358 if (!dbList.isEmpty()) { 359 QMap<QString, MyMoneyPayee> payeesToDelete = q->fetchPayees(dbList, true); 360 Q_FOREACH(const MyMoneyPayee& payee, payeesToDelete) { 361 q->removePayee(payee); 362 } 363 } 364 } 365 writeTags()366 void writeTags() 367 { 368 Q_Q(MyMoneyStorageSql); 369 // first, get a list of what's on the database (see writeInstitutions) 370 QList<QString> dbList; 371 QSqlQuery query(*q); 372 query.prepare("SELECT id FROM kmmTags;"); 373 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Tag list"); // krazy:exclude=crashy 374 while (query.next()) dbList.append(query.value(0).toString()); 375 376 QList<MyMoneyTag> list = m_storage->tagList(); 377 signalProgress(0, list.count(), "Writing Tags..."); 378 QSqlQuery query2(*q); 379 query.prepare(m_db.m_tables["kmmTags"].updateString()); 380 query2.prepare(m_db.m_tables["kmmTags"].insertString()); 381 foreach (const MyMoneyTag& it, list) { 382 if (dbList.contains(it.id())) { 383 dbList.removeAll(it.id()); 384 writeTag(it, query); 385 } else { 386 writeTag(it, query2); 387 } 388 signalProgress(++m_tags, 0); 389 } 390 391 if (!dbList.isEmpty()) { 392 QVariantList deleteList; 393 // qCopy segfaults here, so do it with a hand-rolled loop 394 foreach (const QString& it, dbList) { 395 deleteList << it; 396 } 397 query.prepare(m_db.m_tables["kmmTags"].deleteString()); 398 query.bindValue(":id", deleteList); 399 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Tag"); 400 m_tags -= query.numRowsAffected(); 401 } 402 } 403 writeAccounts()404 void writeAccounts() 405 { 406 Q_Q(MyMoneyStorageSql); 407 // first, get a list of what's on the database (see writeInstitutions) 408 QList<QString> dbList; 409 QSqlQuery query(*q); 410 query.prepare("SELECT id FROM kmmAccounts;"); 411 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Account list"); // krazy:exclude=crashy 412 while (query.next()) dbList.append(query.value(0).toString()); 413 414 QList<MyMoneyAccount> list; 415 m_storage->accountList(list); 416 unsigned progress = 0; 417 signalProgress(0, list.count(), "Writing Accounts..."); 418 if (dbList.isEmpty()) { // new table, insert standard accounts 419 query.prepare(m_db.m_tables["kmmAccounts"].insertString()); 420 } else { 421 query.prepare(m_db.m_tables["kmmAccounts"].updateString()); 422 } 423 // Attempt to write the standard accounts. For an empty db, this will fail. 424 try { 425 QList<MyMoneyAccount> stdList; 426 stdList << m_storage->asset(); 427 stdList << m_storage->liability(); 428 stdList << m_storage->expense(); 429 stdList << m_storage->income(); 430 stdList << m_storage->equity(); 431 writeAccountList(stdList, query); 432 m_accounts += stdList.size(); 433 } catch (const MyMoneyException &) { 434 // If the above failed, assume that the database is empty and create 435 // the standard accounts by hand before writing them. 436 MyMoneyAccount acc_l; 437 acc_l.setAccountType(Account::Type::Liability); 438 acc_l.setName("Liability"); 439 MyMoneyAccount liability(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Liability), acc_l); 440 441 MyMoneyAccount acc_a; 442 acc_a.setAccountType(Account::Type::Asset); 443 acc_a.setName("Asset"); 444 MyMoneyAccount asset(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Asset), acc_a); 445 446 MyMoneyAccount acc_e; 447 acc_e.setAccountType(Account::Type::Expense); 448 acc_e.setName("Expense"); 449 MyMoneyAccount expense(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Expense), acc_e); 450 451 MyMoneyAccount acc_i; 452 acc_i.setAccountType(Account::Type::Income); 453 acc_i.setName("Income"); 454 MyMoneyAccount income(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Income), acc_i); 455 456 MyMoneyAccount acc_q; 457 acc_q.setAccountType(Account::Type::Equity); 458 acc_q.setName("Equity"); 459 MyMoneyAccount equity(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Equity), acc_q); 460 461 QList<MyMoneyAccount> stdList; 462 stdList << asset; 463 stdList << liability; 464 stdList << expense; 465 stdList << income; 466 stdList << equity; 467 writeAccountList(stdList, query); 468 m_accounts += stdList.size(); 469 } 470 471 QSqlQuery query2(*q); 472 query.prepare(m_db.m_tables["kmmAccounts"].updateString()); 473 query2.prepare(m_db.m_tables["kmmAccounts"].insertString()); 474 QList<MyMoneyAccount> updateList; 475 QList<MyMoneyAccount> insertList; 476 // Update the accounts that exist; insert the ones that do not. 477 foreach (const MyMoneyAccount& it, list) { 478 m_transactionCountMap[it.id()] = m_storage->transactionCount(it.id()); 479 if (dbList.contains(it.id())) { 480 dbList.removeAll(it.id()); 481 updateList << it; 482 } else { 483 insertList << it; 484 } 485 signalProgress(++progress, 0); 486 ++m_accounts; 487 } 488 489 writeAccountList(updateList, query); 490 writeAccountList(insertList, query2); 491 492 // Delete the accounts that are in the db but no longer in memory. 493 if (!dbList.isEmpty()) { 494 QVariantList kvpList; 495 496 query.prepare("DELETE FROM kmmAccounts WHERE id = :id"); 497 foreach (const QString& it, dbList) { 498 if (!m_storage->isStandardAccount(it)) { 499 kvpList << it; 500 } 501 } 502 query.bindValue(":id", kvpList); 503 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Account"); 504 505 deleteKeyValuePairs("ACCOUNT", kvpList); 506 deleteKeyValuePairs("ONLINEBANKING", kvpList); 507 } 508 } 509 writeTransactions()510 void writeTransactions() 511 { 512 Q_Q(MyMoneyStorageSql); 513 // first, get a list of what's on the database (see writeInstitutions) 514 QList<QString> dbList; 515 QSqlQuery query(*q); 516 query.prepare("SELECT id FROM kmmTransactions WHERE txType = 'N';"); 517 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Transaction list"); // krazy:exclude=crashy 518 while (query.next()) dbList.append(query.value(0).toString()); 519 520 MyMoneyTransactionFilter filter; 521 filter.setReportAllSplits(false); 522 QList<MyMoneyTransaction> list; 523 m_storage->transactionList(list, filter); 524 signalProgress(0, list.count(), "Writing Transactions..."); 525 QSqlQuery q2(*q); 526 query.prepare(m_db.m_tables["kmmTransactions"].updateString()); 527 q2.prepare(m_db.m_tables["kmmTransactions"].insertString()); 528 foreach (const MyMoneyTransaction& it, list) { 529 if (dbList.contains(it.id())) { 530 dbList.removeAll(it.id()); 531 writeTransaction(it.id(), it, query, "N"); 532 } else { 533 writeTransaction(it.id(), it, q2, "N"); 534 } 535 signalProgress(++m_transactions, 0); 536 } 537 538 if (!dbList.isEmpty()) { 539 foreach (const QString& it, dbList) { 540 deleteTransaction(it); 541 } 542 } 543 } 544 writeSchedules()545 void writeSchedules() 546 { 547 Q_Q(MyMoneyStorageSql); 548 // first, get a list of what's on the database (see writeInstitutions) 549 QList<QString> dbList; 550 QSqlQuery query(*q); 551 query.prepare("SELECT id FROM kmmSchedules;"); 552 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Schedule list"); // krazy:exclude=crashy 553 while (query.next()) dbList.append(query.value(0).toString()); 554 555 const auto list = m_storage->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, 556 QDate(), QDate(), false); 557 QSqlQuery query2(*q); 558 //TODO: find a way to prepare the queries outside of the loop. writeSchedule() 559 // modifies the query passed to it, so they have to be re-prepared every pass. 560 signalProgress(0, list.count(), "Writing Schedules..."); 561 foreach (const MyMoneySchedule& it, list) { 562 query.prepare(m_db.m_tables["kmmSchedules"].updateString()); 563 query2.prepare(m_db.m_tables["kmmSchedules"].insertString()); 564 bool insert = true; 565 if (dbList.contains(it.id())) { 566 dbList.removeAll(it.id()); 567 insert = false; 568 writeSchedule(it, query, insert); 569 } else { 570 writeSchedule(it, query2, insert); 571 } 572 signalProgress(++m_schedules, 0); 573 } 574 575 if (!dbList.isEmpty()) { 576 foreach (const QString& it, dbList) { 577 deleteSchedule(it); 578 } 579 } 580 } 581 writeSecurities()582 void writeSecurities() 583 { 584 Q_Q(MyMoneyStorageSql); 585 // first, get a list of what's on the database (see writeInstitutions) 586 QList<QString> dbList; 587 QSqlQuery query(*q); 588 QSqlQuery query2(*q); 589 query.prepare("SELECT id FROM kmmSecurities;"); 590 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building security list"); // krazy:exclude=crashy 591 while (query.next()) dbList.append(query.value(0).toString()); 592 593 const QList<MyMoneySecurity> securityList = m_storage->securityList(); 594 signalProgress(0, securityList.count(), "Writing Securities..."); 595 query.prepare(m_db.m_tables["kmmSecurities"].updateString()); 596 query2.prepare(m_db.m_tables["kmmSecurities"].insertString()); 597 foreach (const MyMoneySecurity& it, securityList) { 598 if (dbList.contains(it.id())) { 599 dbList.removeAll(it.id()); 600 writeSecurity(it, query); 601 } else { 602 writeSecurity(it, query2); 603 } 604 signalProgress(++m_securities, 0); 605 } 606 607 if (!dbList.isEmpty()) { 608 QVariantList idList; 609 // qCopy segfaults here, so do it with a hand-rolled loop 610 foreach (const QString& it, dbList) { 611 idList << it; 612 } 613 614 query.prepare("DELETE FROM kmmSecurities WHERE id = :id"); 615 query2.prepare("DELETE FROM kmmPrices WHERE fromId = :id OR toId = :id"); 616 query.bindValue(":id", idList); 617 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Security"); 618 619 query2.bindValue(":fromId", idList); 620 query2.bindValue(":toId", idList); 621 if (!query2.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Security"); 622 623 deleteKeyValuePairs("SECURITY", idList); 624 } 625 } 626 writePrices()627 void writePrices() 628 { 629 Q_Q(MyMoneyStorageSql); 630 // due to difficulties in matching and determining deletes 631 // easiest way is to delete all and re-insert 632 QSqlQuery query(*q); 633 query.prepare("DELETE FROM kmmPrices"); 634 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("deleting Prices"); // krazy:exclude=crashy 635 m_prices = 0; 636 637 const MyMoneyPriceList list = m_storage->priceList(); 638 signalProgress(0, list.count(), "Writing Prices..."); 639 MyMoneyPriceList::ConstIterator it; 640 for (it = list.constBegin(); it != list.constEnd(); ++it) { 641 writePricePair(*it); 642 } 643 } 644 writeCurrencies()645 void writeCurrencies() 646 { 647 Q_Q(MyMoneyStorageSql); 648 // first, get a list of what's on the database (see writeInstitutions) 649 QList<QString> dbList; 650 QSqlQuery query(*q); 651 QSqlQuery query2(*q); 652 query.prepare("SELECT ISOCode FROM kmmCurrencies;"); 653 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Currency list"); // krazy:exclude=crashy 654 while (query.next()) dbList.append(query.value(0).toString()); 655 656 const QList<MyMoneySecurity> currencyList = m_storage->currencyList(); 657 signalProgress(0, currencyList.count(), "Writing Currencies..."); 658 query.prepare(m_db.m_tables["kmmCurrencies"].updateString()); 659 query2.prepare(m_db.m_tables["kmmCurrencies"].insertString()); 660 foreach (const MyMoneySecurity& it, currencyList) { 661 if (dbList.contains(it.id())) { 662 dbList.removeAll(it.id()); 663 writeCurrency(it, query); 664 } else { 665 writeCurrency(it, query2); 666 } 667 signalProgress(++m_currencies, 0); 668 } 669 670 if (!dbList.isEmpty()) { 671 QVariantList isoCodeList; 672 query.prepare("DELETE FROM kmmCurrencies WHERE ISOCode = :ISOCode"); 673 // qCopy segfaults here, so do it with a hand-rolled loop 674 foreach (const QString& it, dbList) { 675 isoCodeList << it; 676 } 677 678 query.bindValue(":ISOCode", isoCodeList); 679 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Currency"); 680 } 681 } 682 writeFileInfo()683 void writeFileInfo() 684 { 685 Q_Q(MyMoneyStorageSql); 686 // we have no real way of knowing when these change, so re-write them every time 687 QVariantList kvpList; 688 kvpList << ""; 689 QList<QMap<QString, QString> > pairs; 690 pairs << m_storage->pairs(); 691 deleteKeyValuePairs("STORAGE", kvpList); 692 writeKeyValuePairs("STORAGE", kvpList, pairs); 693 694 QSqlQuery query(*q); 695 query.prepare("SELECT count(*) FROM kmmFileInfo;"); 696 if (!query.exec() || !query.next()) 697 throw MYMONEYEXCEPTIONSQL("checking fileinfo"); // krazy:exclude=crashy 698 699 if (query.value(0).toInt() == 0) { 700 // Cannot use "INSERT INTO kmmFileInfo DEFAULT VALUES;" because it is not supported by MySQL 701 query.prepare(QLatin1String("INSERT INTO kmmFileInfo (version) VALUES (null);")); 702 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("inserting fileinfo"); // krazy:exclude=crashy 703 } 704 705 query.prepare(QLatin1String( 706 "UPDATE kmmFileInfo SET " 707 "version = :version, " 708 "fixLevel = :fixLevel, " 709 "created = :created, " 710 "lastModified = :lastModified, " 711 "baseCurrency = :baseCurrency, " 712 "dateRangeStart = :dateRangeStart, " 713 "dateRangeEnd = :dateRangeEnd, " 714 "hiInstitutionId = :hiInstitutionId, " 715 "hiPayeeId = :hiPayeeId, " 716 "hiTagId = :hiTagId, " 717 "hiAccountId = :hiAccountId, " 718 "hiTransactionId = :hiTransactionId, " 719 "hiScheduleId = :hiScheduleId, " 720 "hiSecurityId = :hiSecurityId, " 721 "hiReportId = :hiReportId, " 722 "hiBudgetId = :hiBudgetId, " 723 "hiOnlineJobId = :hiOnlineJobId, " 724 "hiPayeeIdentifierId = :hiPayeeIdentifierId, " 725 "encryptData = :encryptData, " 726 "updateInProgress = :updateInProgress, " 727 "logonUser = :logonUser, " 728 "logonAt = :logonAt, " 729 //! @todo The following updates are for backwards compatibility only 730 //! remove backwards compatibility in a later version 731 "institutions = :institutions, " 732 "accounts = :accounts, " 733 "payees = :payees, " 734 "tags = :tags, " 735 "transactions = :transactions, " 736 "splits = :splits, " 737 "securities = :securities, " 738 "prices = :prices, " 739 "currencies = :currencies, " 740 "schedules = :schedules, " 741 "reports = :reports, " 742 "kvps = :kvps, " 743 "budgets = :budgets; " 744 ) 745 ); 746 747 query.bindValue(":version", m_dbVersion); 748 query.bindValue(":fixLevel", m_storage->fileFixVersion()); 749 query.bindValue(":created", m_storage->creationDate().toString(Qt::ISODate)); 750 //q.bindValue(":lastModified", m_storage->lastModificationDate().toString(Qt::ISODate)); 751 query.bindValue(":lastModified", QDate::currentDate().toString(Qt::ISODate)); 752 query.bindValue(":baseCurrency", m_storage->pairs()["kmm-baseCurrency"]); 753 query.bindValue(":dateRangeStart", QDate()); 754 query.bindValue(":dateRangeEnd", QDate()); 755 756 //FIXME: This modifies all m_<variable> used in this function. 757 // Sometimes the memory has been updated. 758 759 // Should most of these be tracked in a view? 760 // Variables actually needed are: version, fileFixVersion, creationDate, 761 // baseCurrency, encryption, update info, and logon info. 762 //try { 763 //readFileInfo(); 764 //} catch (...) { 765 //q->startCommitUnit(Q_FUNC_INFO); 766 //} 767 768 //! @todo The following bindings are for backwards compatibility only 769 //! remove backwards compatibility in a later version 770 query.bindValue(":hiInstitutionId", QVariant::fromValue(q->getNextInstitutionId())); 771 query.bindValue(":hiPayeeId", QVariant::fromValue(q->getNextPayeeId())); 772 query.bindValue(":hiTagId", QVariant::fromValue(q->getNextTagId())); 773 query.bindValue(":hiAccountId", QVariant::fromValue(q->getNextAccountId())); 774 query.bindValue(":hiTransactionId", QVariant::fromValue(q->getNextTransactionId())); 775 query.bindValue(":hiScheduleId", QVariant::fromValue(q->getNextScheduleId())); 776 query.bindValue(":hiSecurityId", QVariant::fromValue(q->getNextSecurityId())); 777 query.bindValue(":hiReportId", QVariant::fromValue(q->getNextReportId())); 778 query.bindValue(":hiBudgetId", QVariant::fromValue(q->getNextBudgetId())); 779 query.bindValue(":hiOnlineJobId", QVariant::fromValue(q->getNextOnlineJobId())); 780 query.bindValue(":hiPayeeIdentifierId", QVariant::fromValue(q->getNextPayeeIdentifierId())); 781 782 query.bindValue(":encryptData", m_encryptData); 783 query.bindValue(":updateInProgress", "N"); 784 query.bindValue(":logonUser", m_logonUser); 785 query.bindValue(":logonAt", m_logonAt.toString(Qt::ISODate)); 786 787 //! @todo The following bindings are for backwards compatibility only 788 //! remove backwards compatibility in a later version 789 query.bindValue(":institutions", (unsigned long long) m_institutions); 790 query.bindValue(":accounts", (unsigned long long) m_accounts); 791 query.bindValue(":payees", (unsigned long long) m_payees); 792 query.bindValue(":tags", (unsigned long long) m_tags); 793 query.bindValue(":transactions", (unsigned long long) m_transactions); 794 query.bindValue(":splits", (unsigned long long) m_splits); 795 query.bindValue(":securities", (unsigned long long) m_securities); 796 query.bindValue(":prices", (unsigned long long) m_prices); 797 query.bindValue(":currencies", (unsigned long long) m_currencies); 798 query.bindValue(":schedules", (unsigned long long) m_schedules); 799 query.bindValue(":reports", (unsigned long long) m_reports); 800 query.bindValue(":kvps", (unsigned long long) m_kvps); 801 query.bindValue(":budgets", (unsigned long long) m_budgets); 802 803 if (!query.exec()) 804 throw MYMONEYEXCEPTIONSQL("writing FileInfo"); // krazy:exclude=crashy 805 } 806 writeReports()807 void writeReports() 808 { 809 Q_Q(MyMoneyStorageSql); 810 // first, get a list of what's on the database (see writeInstitutions) 811 QList<QString> dbList; 812 QSqlQuery query(*q); 813 QSqlQuery query2(*q); 814 query.prepare("SELECT id FROM kmmReportConfig;"); 815 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Report list"); // krazy:exclude=crashy 816 while (query.next()) dbList.append(query.value(0).toString()); 817 818 QList<MyMoneyReport> list = m_storage->reportList(); 819 signalProgress(0, list.count(), "Writing Reports..."); 820 query.prepare(m_db.m_tables["kmmReportConfig"].updateString()); 821 query2.prepare(m_db.m_tables["kmmReportConfig"].insertString()); 822 foreach (const MyMoneyReport& it, list) { 823 if (dbList.contains(it.id())) { 824 dbList.removeAll(it.id()); 825 writeReport(it, query); 826 } else { 827 writeReport(it, query2); 828 } 829 signalProgress(++m_reports, 0); 830 } 831 832 if (!dbList.isEmpty()) { 833 QVariantList idList; 834 query.prepare("DELETE FROM kmmReportConfig WHERE id = :id"); 835 // qCopy segfaults here, so do it with a hand-rolled loop 836 foreach (const QString& it, dbList) { 837 idList << it; 838 } 839 840 query.bindValue(":id", idList); 841 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Report"); 842 } 843 } 844 writeBudgets()845 void writeBudgets() 846 { 847 Q_Q(MyMoneyStorageSql); 848 // first, get a list of what's on the database (see writeInstitutions) 849 QList<QString> dbList; 850 QSqlQuery query(*q); 851 QSqlQuery query2(*q); 852 query.prepare("SELECT name FROM kmmBudgetConfig;"); 853 if (!query.exec()) 854 throw MYMONEYEXCEPTIONSQL("building Budget list"); // krazy:exclude=crashy 855 while (query.next()) 856 dbList.append(query.value(0).toString()); 857 858 QList<MyMoneyBudget> list = m_storage->budgetList(); 859 signalProgress(0, list.count(), "Writing Budgets..."); 860 query.prepare(m_db.m_tables["kmmBudgetConfig"].updateString()); 861 query2.prepare(m_db.m_tables["kmmBudgetConfig"].insertString()); 862 foreach (const MyMoneyBudget& it, list) { 863 if (dbList.contains(it.name())) { 864 dbList.removeAll(it.name()); 865 writeBudget(it, query); 866 } else { 867 writeBudget(it, query2); 868 } 869 signalProgress(++m_budgets, 0); 870 } 871 872 if (!dbList.isEmpty()) { 873 QVariantList idList; 874 query.prepare("DELETE FROM kmmBudgetConfig WHERE id = :id"); 875 // qCopy segfaults here, so do it with a hand-rolled loop 876 foreach (const QString& it, dbList) { 877 idList << it; 878 } 879 880 query.bindValue(":name", idList); 881 if (!query.execBatch()) 882 throw MYMONEYEXCEPTIONSQL("deleting Budget"); 883 } 884 } 885 clearTable(const QString & tableName,QSqlQuery & query)886 bool clearTable(const QString& tableName, QSqlQuery& query) 887 { 888 if (query.exec(QString("SELECT count(*) FROM %1").arg(tableName))) { 889 if (query.next()) { 890 if (query.value(0).toUInt() > 0) { 891 if (!query.exec(QString("DELETE FROM %1").arg(tableName))) 892 return false; 893 } 894 } 895 } 896 return true; 897 } 898 writeOnlineJobs()899 void writeOnlineJobs() 900 { 901 Q_Q(MyMoneyStorageSql); 902 QSqlQuery query(*q); 903 if (!clearTable(QStringLiteral("kmmOnlineJobs"), query)) 904 throw MYMONEYEXCEPTIONSQL("Clean kmmOnlineJobs table"); 905 906 if (!clearTable(QStringLiteral("kmmSepaOrders"), query)) 907 throw MYMONEYEXCEPTIONSQL("Clean kmmSepaOrders table"); 908 909 if (!clearTable(QStringLiteral("kmmNationalAccountNumber"), query)) 910 throw MYMONEYEXCEPTIONSQL("Clean kmmNationalAccountNumber table"); 911 912 const QList<onlineJob> jobs(m_storage->onlineJobList()); 913 signalProgress(0, jobs.count(), i18n("Inserting online jobs.")); 914 // Create list for onlineJobs which failed and the reason therefor 915 QList<QPair<onlineJob, QString> > failedJobs; 916 int jobCount = 0; 917 foreach (const onlineJob& job, jobs) { 918 try { 919 q->addOnlineJob(job); 920 } catch (const MyMoneyException &e) { 921 // Do not save e as this may point to an inherited class 922 failedJobs.append(QPair<onlineJob, QString>(job, e.what())); 923 qDebug() << "Failed to save onlineJob" << job.id() << "Reason:" << e.what(); 924 } 925 926 signalProgress(++jobCount, 0); 927 } 928 929 if (!failedJobs.isEmpty()) { 930 /** @todo Improve error message */ 931 throw MYMONEYEXCEPTION_CSTRING("Could not save onlineJob."); 932 } 933 } 934 /** @} */ 935 936 /** 937 * @name writeMethods 938 * @{ 939 * These methods bind the data fields of MyMoneyObjects to a given query and execute the query. 940 * This is helpful as the query has usually an update and a insert format. 941 */ writeInstitutionList(const QList<MyMoneyInstitution> & iList,QSqlQuery & query)942 void writeInstitutionList(const QList<MyMoneyInstitution>& iList, QSqlQuery& query) 943 { 944 QVariantList idList; 945 QVariantList nameList; 946 QVariantList managerList; 947 QVariantList routingCodeList; 948 QVariantList addressStreetList; 949 QVariantList addressCityList; 950 QVariantList addressZipcodeList; 951 QVariantList telephoneList; 952 QList<QMap<QString, QString> > kvpPairsList; 953 954 foreach (const MyMoneyInstitution& i, iList) { 955 idList << i.id(); 956 nameList << i.name(); 957 managerList << i.manager(); 958 routingCodeList << i.sortcode(); 959 addressStreetList << i.street(); 960 addressCityList << i.city(); 961 addressZipcodeList << i.postcode(); 962 telephoneList << i.telephone(); 963 kvpPairsList << i.pairs(); 964 } 965 966 query.bindValue(":id", idList); 967 query.bindValue(":name", nameList); 968 query.bindValue(":manager", managerList); 969 query.bindValue(":routingCode", routingCodeList); 970 query.bindValue(":addressStreet", addressStreetList); 971 query.bindValue(":addressCity", addressCityList); 972 query.bindValue(":addressZipcode", addressZipcodeList); 973 query.bindValue(":telephone", telephoneList); 974 975 if (!query.execBatch()) 976 throw MYMONEYEXCEPTIONSQL("writing Institution"); 977 deleteKeyValuePairs("INSTITUTION", idList); 978 // delete the old version in any case 979 deleteKeyValuePairs("OFXSETTINGS", idList); 980 writeKeyValuePairs("INSTITUTION", idList, kvpPairsList); 981 // Set m_hiIdInstitutions to 0 to force recalculation the next time it is requested 982 m_hiIdInstitutions = 0; 983 } 984 985 void writePayee(const MyMoneyPayee& p, QSqlQuery& query, bool isUserInfo = false) 986 { 987 if (isUserInfo) { 988 query.bindValue(":id", "USER"); 989 } else { 990 query.bindValue(":id", p.id()); 991 } 992 query.bindValue(":name", p.name()); 993 query.bindValue(":reference", p.reference()); 994 query.bindValue(":email", p.email()); 995 query.bindValue(":addressStreet", p.address()); 996 query.bindValue(":addressCity", p.city()); 997 query.bindValue(":addressZipcode", p.postcode()); 998 query.bindValue(":addressState", p.state()); 999 query.bindValue(":telephone", p.telephone()); 1000 query.bindValue(":notes", p.notes()); 1001 query.bindValue(":defaultAccountId", p.defaultAccountId()); 1002 bool ignoreCase; 1003 QString matchKeys; 1004 auto type = p.matchData(ignoreCase, matchKeys); 1005 query.bindValue(":matchData", static_cast<uint>(type)); 1006 1007 if (ignoreCase) 1008 query.bindValue(":matchIgnoreCase", "Y"); 1009 else 1010 query.bindValue(":matchIgnoreCase", "N"); 1011 1012 query.bindValue(":matchKeys", matchKeys); 1013 if (!query.exec()) // krazy:exclude=crashy 1014 throw MYMONEYEXCEPTIONSQL("writing Payee"); // krazy:exclude=crashy 1015 1016 if (!isUserInfo) 1017 m_hiIdPayees = 0; 1018 } 1019 writeTag(const MyMoneyTag & ta,QSqlQuery & query)1020 void writeTag(const MyMoneyTag& ta, QSqlQuery& query) 1021 { 1022 query.bindValue(":id", ta.id()); 1023 query.bindValue(":name", ta.name()); 1024 query.bindValue(":tagColor", ta.tagColor().name()); 1025 if (ta.isClosed()) query.bindValue(":closed", "Y"); 1026 else query.bindValue(":closed", "N"); 1027 query.bindValue(":notes", ta.notes()); 1028 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Tag"); // krazy:exclude=crashy 1029 m_hiIdTags = 0; 1030 } 1031 writeAccountList(const QList<MyMoneyAccount> & accList,QSqlQuery & query)1032 void writeAccountList(const QList<MyMoneyAccount>& accList, QSqlQuery& query) 1033 { 1034 //MyMoneyMoney balance = m_storagePtr->balance(acc.id(), QDate()); 1035 1036 QVariantList idList; 1037 QVariantList institutionIdList; 1038 QVariantList parentIdList; 1039 QVariantList lastReconciledList; 1040 QVariantList lastModifiedList; 1041 QVariantList openingDateList; 1042 QVariantList accountNumberList; 1043 QVariantList accountTypeList; 1044 QVariantList accountTypeStringList; 1045 QVariantList isStockAccountList; 1046 QVariantList accountNameList; 1047 QVariantList descriptionList; 1048 QVariantList currencyIdList; 1049 QVariantList balanceList; 1050 QVariantList balanceFormattedList; 1051 QVariantList transactionCountList; 1052 1053 QList<QMap<QString, QString> > pairs; 1054 QList<QMap<QString, QString> > onlineBankingPairs; 1055 1056 foreach (const MyMoneyAccount& a, accList) { 1057 idList << a.id(); 1058 institutionIdList << a.institutionId(); 1059 parentIdList << a.parentAccountId(); 1060 if (a.lastReconciliationDate() == QDate()) 1061 lastReconciledList << a.lastReconciliationDate(); 1062 else 1063 lastReconciledList << a.lastReconciliationDate().toString(Qt::ISODate); 1064 lastModifiedList << a.lastModified(); 1065 if (a.openingDate() == QDate()) 1066 openingDateList << a.openingDate(); 1067 else 1068 openingDateList << a.openingDate().toString(Qt::ISODate); 1069 accountNumberList << a.number(); 1070 accountTypeList << (int)a.accountType(); 1071 accountTypeStringList << MyMoneyAccount::accountTypeToString(a.accountType()); 1072 if (a.accountType() == Account::Type::Stock) 1073 isStockAccountList << "Y"; 1074 else 1075 isStockAccountList << "N"; 1076 accountNameList << a.name(); 1077 descriptionList << a.description(); 1078 currencyIdList << a.currencyId(); 1079 // This section attempts to get the balance from the database, if possible 1080 // That way, the balance fields are kept in sync. If that fails, then 1081 // It is assumed that the account actually knows its correct balance. 1082 1083 //FIXME: Using exceptions for branching always feels like a kludge. 1084 // Look for a better way. 1085 try { 1086 MyMoneyMoney bal = m_storage->balance(a.id(), QDate()); 1087 balanceList << bal.toString(); 1088 balanceFormattedList << bal.formatMoney("", -1, false); 1089 } catch (const MyMoneyException &) { 1090 balanceList << a.balance().toString(); 1091 balanceFormattedList << a.balance().formatMoney("", -1, false); 1092 } 1093 transactionCountList << quint64(m_transactionCountMap[a.id()]); 1094 1095 //MMAccount inherits from KVPContainer AND has a KVPContainer member 1096 //so handle both 1097 pairs << a.pairs(); 1098 onlineBankingPairs << a.onlineBankingSettings().pairs(); 1099 } 1100 1101 query.bindValue(":id", idList); 1102 query.bindValue(":institutionId", institutionIdList); 1103 query.bindValue(":parentId", parentIdList); 1104 query.bindValue(":lastReconciled", lastReconciledList); 1105 query.bindValue(":lastModified", lastModifiedList); 1106 query.bindValue(":openingDate", openingDateList); 1107 query.bindValue(":accountNumber", accountNumberList); 1108 query.bindValue(":accountType", accountTypeList); 1109 query.bindValue(":accountTypeString", accountTypeStringList); 1110 query.bindValue(":isStockAccount", isStockAccountList); 1111 query.bindValue(":accountName", accountNameList); 1112 query.bindValue(":description", descriptionList); 1113 query.bindValue(":currencyId", currencyIdList); 1114 query.bindValue(":balance", balanceList); 1115 query.bindValue(":balanceFormatted", balanceFormattedList); 1116 query.bindValue(":transactionCount", transactionCountList); 1117 1118 if (!query.execBatch()) 1119 throw MYMONEYEXCEPTIONSQL("writing Account"); 1120 1121 //Add in Key-Value Pairs for accounts. 1122 deleteKeyValuePairs("ACCOUNT", idList); 1123 deleteKeyValuePairs("ONLINEBANKING", idList); 1124 writeKeyValuePairs("ACCOUNT", idList, pairs); 1125 writeKeyValuePairs("ONLINEBANKING", idList, onlineBankingPairs); 1126 m_hiIdAccounts = 0; 1127 } 1128 writeTransaction(const QString & txId,const MyMoneyTransaction & tx,QSqlQuery & query,const QString & type)1129 void writeTransaction(const QString& txId, const MyMoneyTransaction& tx, QSqlQuery& query, const QString& type) 1130 { 1131 query.bindValue(":id", txId); 1132 query.bindValue(":txType", type); 1133 query.bindValue(":postDate", tx.postDate().toString(Qt::ISODate)); 1134 query.bindValue(":memo", tx.memo()); 1135 query.bindValue(":entryDate", tx.entryDate().toString(Qt::ISODate)); 1136 query.bindValue(":currencyId", tx.commodity()); 1137 query.bindValue(":bankId", tx.bankID()); 1138 1139 if (!query.exec()) // krazy:exclude=crashy 1140 throw MYMONEYEXCEPTIONSQL("writing Transaction"); // krazy:exclude=crashy 1141 1142 m_txPostDate = tx.postDate(); // FIXME: TEMP till Tom puts date in split object 1143 QList<MyMoneySplit> splitList = tx.splits(); 1144 writeSplits(txId, type, splitList); 1145 1146 //Add in Key-Value Pairs for transactions. 1147 QVariantList idList; 1148 idList << txId; 1149 deleteKeyValuePairs("TRANSACTION", idList); 1150 QList<QMap<QString, QString> > pairs; 1151 pairs << tx.pairs(); 1152 writeKeyValuePairs("TRANSACTION", idList, pairs); 1153 m_hiIdTransactions = 0; 1154 } 1155 writeSplits(const QString & txId,const QString & type,const QList<MyMoneySplit> & splitList)1156 void writeSplits(const QString& txId, const QString& type, const QList<MyMoneySplit>& splitList) 1157 { 1158 Q_Q(MyMoneyStorageSql); 1159 // first, get a list of what's on the database (see writeInstitutions) 1160 QList<uint> dbList; 1161 QList<MyMoneySplit> insertList; 1162 QList<MyMoneySplit> updateList; 1163 QList<int> insertIdList; 1164 QList<int> updateIdList; 1165 QSqlQuery query(*q); 1166 query.prepare("SELECT splitId FROM kmmSplits where transactionId = :id;"); 1167 query.bindValue(":id", txId); 1168 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("building Split list"); // krazy:exclude=crashy 1169 while (query.next()) dbList.append(query.value(0).toUInt()); 1170 1171 QSqlQuery query2(*q); 1172 query.prepare(m_db.m_tables["kmmSplits"].updateString()); 1173 query2.prepare(m_db.m_tables["kmmSplits"].insertString()); 1174 auto i = 0; 1175 for (auto it = splitList.constBegin(); it != splitList.constEnd(); ++it) { 1176 if (dbList.contains(i)) { 1177 dbList.removeAll(i); 1178 updateList << *it; 1179 updateIdList << i; 1180 } else { 1181 ++m_splits; 1182 insertList << *it; 1183 insertIdList << i; 1184 } 1185 ++i; 1186 } 1187 1188 if (!insertList.isEmpty()) { 1189 writeSplitList(txId, insertList, type, insertIdList, query2); 1190 writeTagSplitsList(txId, insertList, insertIdList); 1191 } 1192 1193 if (!updateList.isEmpty()) { 1194 writeSplitList(txId, updateList, type, updateIdList, query); 1195 deleteTagSplitsList(txId, updateIdList); 1196 writeTagSplitsList(txId, updateList, updateIdList); 1197 } 1198 1199 if (!dbList.isEmpty()) { 1200 QVector<QVariant> txIdList(dbList.count(), txId); 1201 QVariantList splitIdList; 1202 query.prepare("DELETE FROM kmmSplits WHERE transactionId = :txId AND splitId = :splitId"); 1203 // qCopy segfaults here, so do it with a hand-rolled loop 1204 foreach (int it, dbList) { 1205 splitIdList << it; 1206 } 1207 query.bindValue(":txId", txIdList.toList()); 1208 query.bindValue(":splitId", splitIdList); 1209 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Splits"); 1210 } 1211 } 1212 writeTagSplitsList(const QString & txId,const QList<MyMoneySplit> & splitList,const QList<int> & splitIdList)1213 void writeTagSplitsList 1214 (const QString& txId, 1215 const QList<MyMoneySplit>& splitList, 1216 const QList<int>& splitIdList) 1217 { 1218 Q_Q(MyMoneyStorageSql); 1219 MyMoneyDbTransaction t(*q, Q_FUNC_INFO); 1220 QVariantList tagIdList; 1221 QVariantList txIdList; 1222 QVariantList splitIdList_TagSplits; 1223 1224 int i = 0, l = 0; 1225 foreach (const MyMoneySplit& s, splitList) { 1226 for (l = 0; l < s.tagIdList().size(); ++l) { 1227 tagIdList << s.tagIdList()[l]; 1228 splitIdList_TagSplits << splitIdList[i]; 1229 txIdList << txId; 1230 } 1231 i++; 1232 } 1233 QSqlQuery query(*q); 1234 query.prepare(m_db.m_tables["kmmTagSplits"].insertString()); 1235 query.bindValue(":tagId", tagIdList); 1236 query.bindValue(":splitId", splitIdList_TagSplits); 1237 query.bindValue(":transactionId", txIdList); 1238 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("writing tagSplits"); 1239 } 1240 writeSplitList(const QString & txId,const QList<MyMoneySplit> & splitList,const QString & type,const QList<int> & splitIdList,QSqlQuery & query)1241 void writeSplitList 1242 (const QString& txId, 1243 const QList<MyMoneySplit>& splitList, 1244 const QString& type, 1245 const QList<int>& splitIdList, 1246 QSqlQuery& query) 1247 { 1248 QVariantList txIdList; 1249 QVariantList typeList; 1250 QVariantList payeeIdList; 1251 QVariantList reconcileDateList; 1252 QVariantList actionList; 1253 QVariantList reconcileFlagList; 1254 QVariantList valueList; 1255 QVariantList valueFormattedList; 1256 QVariantList sharesList; 1257 QVariantList sharesFormattedList; 1258 QVariantList priceList; 1259 QVariantList priceFormattedList; 1260 QVariantList memoList; 1261 QVariantList accountIdList; 1262 QVariantList costCenterIdList; 1263 QVariantList checkNumberList; 1264 QVariantList postDateList; 1265 QVariantList bankIdList; 1266 QVariantList kvpIdList; 1267 QList<QMap<QString, QString> > kvpPairsList; 1268 1269 int i = 0; 1270 foreach (const MyMoneySplit& s, splitList) { 1271 txIdList << txId; 1272 typeList << type; 1273 payeeIdList << s.payeeId(); 1274 if (s.reconcileDate() == QDate()) 1275 reconcileDateList << s.reconcileDate(); 1276 else 1277 reconcileDateList << s.reconcileDate().toString(Qt::ISODate); 1278 actionList << s.action(); 1279 reconcileFlagList << (int)s.reconcileFlag(); 1280 valueList << s.value().toString(); 1281 valueFormattedList << s.value().formatMoney("", -1, false).replace(QChar(','), QChar('.')); 1282 sharesList << s.shares().toString(); 1283 MyMoneyAccount acc = m_storage->account(s.accountId()); 1284 MyMoneySecurity sec = m_storage->security(acc.currencyId()); 1285 sharesFormattedList << s.price(). 1286 formatMoney("", MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()), false). 1287 replace(QChar(','), QChar('.')); 1288 MyMoneyMoney price = s.actualPrice(); 1289 if (!price.isZero()) { 1290 priceList << price.toString(); 1291 priceFormattedList << price.formatMoney 1292 ("", sec.pricePrecision(), false) 1293 .replace(QChar(','), QChar('.')); 1294 } else { 1295 priceList << QString(); 1296 priceFormattedList << QString(); 1297 } 1298 memoList << s.memo(); 1299 accountIdList << s.accountId(); 1300 costCenterIdList << s.costCenterId(); 1301 checkNumberList << s.number(); 1302 postDateList << m_txPostDate.toString(Qt::ISODate); // FIXME: when Tom puts date into split object 1303 bankIdList << s.bankID(); 1304 1305 kvpIdList << QString(txId + QString::number(splitIdList[i])); 1306 kvpPairsList << s.pairs(); 1307 ++i; 1308 } 1309 1310 query.bindValue(":transactionId", txIdList); 1311 query.bindValue(":txType", typeList); 1312 QVariantList iList; 1313 // qCopy segfaults here, so do it with a hand-rolled loop 1314 foreach (int it_s, splitIdList) { 1315 iList << it_s; 1316 } 1317 1318 query.bindValue(":splitId", iList); 1319 query.bindValue(":payeeId", payeeIdList); 1320 query.bindValue(":reconcileDate", reconcileDateList); 1321 query.bindValue(":action", actionList); 1322 query.bindValue(":reconcileFlag", reconcileFlagList); 1323 query.bindValue(":value", valueList); 1324 query.bindValue(":valueFormatted", valueFormattedList); 1325 query.bindValue(":shares", sharesList); 1326 query.bindValue(":sharesFormatted", sharesFormattedList); 1327 query.bindValue(":price", priceList); 1328 query.bindValue(":priceFormatted", priceFormattedList); 1329 query.bindValue(":memo", memoList); 1330 query.bindValue(":accountId", accountIdList); 1331 query.bindValue(":costCenterId", costCenterIdList); 1332 query.bindValue(":checkNumber", checkNumberList); 1333 query.bindValue(":postDate", postDateList); 1334 query.bindValue(":bankId", bankIdList); 1335 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("writing Split"); 1336 deleteKeyValuePairs("SPLIT", kvpIdList); 1337 writeKeyValuePairs("SPLIT", kvpIdList, kvpPairsList); 1338 } 1339 writeSchedule(const MyMoneySchedule & sch,QSqlQuery & query,bool insert)1340 void writeSchedule(const MyMoneySchedule& sch, QSqlQuery& query, bool insert) 1341 { 1342 query.bindValue(":id", sch.id()); 1343 query.bindValue(":name", sch.name()); 1344 query.bindValue(":type", (int)sch.type()); 1345 query.bindValue(":typeString", MyMoneySchedule::scheduleTypeToString(sch.type())); 1346 query.bindValue(":occurence", (int)sch.occurrence()); // krazy:exclude=spelling 1347 query.bindValue(":occurenceMultiplier", sch.occurrenceMultiplier()); // krazy:exclude=spelling 1348 query.bindValue(":occurenceString", sch.occurrenceToString()); // krazy:exclude=spelling 1349 query.bindValue(":paymentType", (int)sch.paymentType()); 1350 query.bindValue(":paymentTypeString", MyMoneySchedule::paymentMethodToString(sch.paymentType())); 1351 query.bindValue(":startDate", sch.startDate().toString(Qt::ISODate)); 1352 query.bindValue(":endDate", sch.endDate().toString(Qt::ISODate)); 1353 if (sch.isFixed()) { 1354 query.bindValue(":fixed", "Y"); 1355 } else { 1356 query.bindValue(":fixed", "N"); 1357 } 1358 if (sch.lastDayInMonth()) { 1359 query.bindValue(":lastDayInMonth", "Y"); 1360 } else { 1361 query.bindValue(":lastDayInMonth", "N"); 1362 } 1363 if (sch.autoEnter()) { 1364 query.bindValue(":autoEnter", "Y"); 1365 } else { 1366 query.bindValue(":autoEnter", "N"); 1367 } 1368 query.bindValue(":lastPayment", sch.lastPayment()); 1369 query.bindValue(":nextPaymentDue", sch.nextDueDate().toString(Qt::ISODate)); 1370 query.bindValue(":weekendOption", (int)sch.weekendOption()); 1371 query.bindValue(":weekendOptionString", MyMoneySchedule::weekendOptionToString(sch.weekendOption())); 1372 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Schedules"); // krazy:exclude=crashy 1373 1374 //store the payment history for this scheduled task. 1375 //easiest way is to delete all and re-insert; it's not a high use table 1376 query.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id;"); 1377 query.bindValue(":id", sch.id()); 1378 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("deleting Schedule Payment History"); // krazy:exclude=crashy 1379 1380 query.prepare(m_db.m_tables["kmmSchedulePaymentHistory"].insertString()); 1381 foreach (const QDate& it, sch.recordedPayments()) { 1382 query.bindValue(":schedId", sch.id()); 1383 query.bindValue(":payDate", it.toString(Qt::ISODate)); 1384 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Schedule Payment History"); // krazy:exclude=crashy 1385 } 1386 1387 //store the transaction data for this task. 1388 if (!insert) { 1389 query.prepare(m_db.m_tables["kmmTransactions"].updateString()); 1390 } else { 1391 query.prepare(m_db.m_tables["kmmTransactions"].insertString()); 1392 } 1393 writeTransaction(sch.id(), sch.transaction(), query, "S"); 1394 1395 //FIXME: enable when schedules have KVPs. 1396 1397 //Add in Key-Value Pairs for transactions. 1398 //deleteKeyValuePairs("SCHEDULE", sch.id()); 1399 //writeKeyValuePairs("SCHEDULE", sch.id(), sch.pairs()); 1400 } 1401 writeSecurity(const MyMoneySecurity & security,QSqlQuery & query)1402 void writeSecurity(const MyMoneySecurity& security, QSqlQuery& query) 1403 { 1404 query.bindValue(":id", security.id()); 1405 query.bindValue(":name", security.name()); 1406 query.bindValue(":symbol", security.tradingSymbol()); 1407 query.bindValue(":type", static_cast<int>(security.securityType())); 1408 query.bindValue(":typeString", MyMoneySecurity::securityTypeToString(security.securityType())); 1409 query.bindValue(":roundingMethod", static_cast<int>(security.roundingMethod())); 1410 query.bindValue(":smallestAccountFraction", security.smallestAccountFraction()); 1411 query.bindValue(":pricePrecision", security.pricePrecision()); 1412 query.bindValue(":tradingCurrency", security.tradingCurrency()); 1413 query.bindValue(":tradingMarket", security.tradingMarket()); 1414 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Securities"); // krazy:exclude=crashy 1415 1416 //Add in Key-Value Pairs for security 1417 QVariantList idList; 1418 idList << security.id(); 1419 QList<QMap<QString, QString> > pairs; 1420 pairs << security.pairs(); 1421 deleteKeyValuePairs("SECURITY", idList); 1422 writeKeyValuePairs("SECURITY", idList, pairs); 1423 m_hiIdSecurities = 0; 1424 } 1425 writePricePair(const MyMoneyPriceEntries & p)1426 void writePricePair(const MyMoneyPriceEntries& p) 1427 { 1428 MyMoneyPriceEntries::ConstIterator it; 1429 for (it = p.constBegin(); it != p.constEnd(); ++it) { 1430 writePrice(*it); 1431 signalProgress(++m_prices, 0); 1432 } 1433 } 1434 writePrice(const MyMoneyPrice & p)1435 void writePrice(const MyMoneyPrice& p) 1436 { 1437 Q_Q(MyMoneyStorageSql); 1438 QSqlQuery query(*q); 1439 query.prepare(m_db.m_tables["kmmPrices"].insertString()); 1440 query.bindValue(":fromId", p.from()); 1441 query.bindValue(":toId", p.to()); 1442 query.bindValue(":priceDate", p.date().toString(Qt::ISODate)); 1443 query.bindValue(":price", p.rate(QString()).toString()); 1444 query.bindValue(":priceFormatted", p.rate(QString()).formatMoney("", 2)); 1445 query.bindValue(":priceSource", p.source()); 1446 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Prices"); // krazy:exclude=crashy 1447 } 1448 writeCurrency(const MyMoneySecurity & currency,QSqlQuery & query)1449 void writeCurrency(const MyMoneySecurity& currency, QSqlQuery& query) 1450 { 1451 query.bindValue(":ISOcode", currency.id()); 1452 query.bindValue(":name", currency.name()); 1453 query.bindValue(":type", static_cast<int>(currency.securityType())); 1454 query.bindValue(":typeString", MyMoneySecurity::securityTypeToString(currency.securityType())); 1455 // writing the symbol as three short ints is a PITA, but the 1456 // problem is that database drivers have incompatible ways of declaring UTF8 1457 QString symbol = currency.tradingSymbol() + " "; 1458 const ushort* symutf = symbol.utf16(); 1459 //int ix = 0; 1460 //while (x[ix] != '\0') qDebug() << "symbol" << symbol << "char" << ix << "=" << x[ix++]; 1461 //q.bindValue(":symbol1", symbol.mid(0,1).unicode()->unicode()); 1462 //q.bindValue(":symbol2", symbol.mid(1,1).unicode()->unicode()); 1463 //q.bindValue(":symbol3", symbol.mid(2,1).unicode()->unicode()); 1464 query.bindValue(":symbol1", symutf[0]); 1465 query.bindValue(":symbol2", symutf[1]); 1466 query.bindValue(":symbol3", symutf[2]); 1467 query.bindValue(":symbolString", symbol); 1468 query.bindValue(":smallestCashFraction", currency.smallestCashFraction()); 1469 query.bindValue(":smallestAccountFraction", currency.smallestAccountFraction()); 1470 query.bindValue(":pricePrecision", currency.pricePrecision()); 1471 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Currencies"); // krazy:exclude=crashy 1472 } 1473 writeReport(const MyMoneyReport & rep,QSqlQuery & query)1474 void writeReport(const MyMoneyReport& rep, QSqlQuery& query) 1475 { 1476 QDomDocument d; // create a dummy XML document 1477 QDomElement e = d.createElement("REPORTS"); 1478 d.appendChild(e); 1479 MyMoneyXmlContentHandler2::writeReport(rep, d, e); // write the XML to document 1480 query.bindValue(":id", rep.id()); 1481 query.bindValue(":name", rep.name()); 1482 query.bindValue(":XML", d.toString()); 1483 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("writing Reports"); // krazy:exclude=crashy 1484 } 1485 writeBudget(const MyMoneyBudget & bud,QSqlQuery & query)1486 void writeBudget(const MyMoneyBudget& bud, QSqlQuery& query) 1487 { 1488 QDomDocument d; // create a dummy XML document 1489 QDomElement e = d.createElement("BUDGETS"); 1490 d.appendChild(e); 1491 MyMoneyXmlContentHandler2::writeBudget(bud, d, e); // write the XML to document 1492 query.bindValue(":id", bud.id()); 1493 query.bindValue(":name", bud.name()); 1494 query.bindValue(":start", bud.budgetStart()); 1495 query.bindValue(":XML", d.toString()); 1496 if (!query.exec()) // krazy:exclude=crashy 1497 throw MYMONEYEXCEPTIONSQL("writing Budgets"); // krazy:exclude=crashy 1498 } 1499 writeKeyValuePairs(const QString & kvpType,const QVariantList & kvpId,const QList<QMap<QString,QString>> & pairs)1500 void writeKeyValuePairs(const QString& kvpType, const QVariantList& kvpId, const QList<QMap<QString, QString> >& pairs) 1501 { 1502 Q_Q(MyMoneyStorageSql); 1503 if (pairs.empty()) 1504 return; 1505 1506 QVariantList type; 1507 QVariantList id; 1508 QVariantList key; 1509 QVariantList value; 1510 int pairCount = 0; 1511 1512 for (int i = 0; i < kvpId.size(); ++i) { 1513 QMap<QString, QString>::ConstIterator it; 1514 for (it = pairs[i].constBegin(); it != pairs[i].constEnd(); ++it) { 1515 type << kvpType; 1516 id << kvpId[i]; 1517 key << it.key(); 1518 value << it.value(); 1519 } 1520 pairCount += pairs[i].size(); 1521 } 1522 1523 QSqlQuery query(*q); 1524 query.prepare(m_db.m_tables["kmmKeyValuePairs"].insertString()); 1525 query.bindValue(":kvpType", type); 1526 query.bindValue(":kvpId", id); 1527 query.bindValue(":kvpKey", key); 1528 query.bindValue(":kvpData", value); 1529 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("writing KVP"); 1530 m_kvps += pairCount; 1531 } 1532 writeOnlineJob(const onlineJob & job,QSqlQuery & query)1533 void writeOnlineJob(const onlineJob& job, QSqlQuery& query) 1534 { 1535 Q_ASSERT(job.id().startsWith('O')); 1536 1537 query.bindValue(":id", job.id()); 1538 query.bindValue(":type", job.taskIid()); 1539 query.bindValue(":jobSend", job.sendDate()); 1540 query.bindValue(":bankAnswerDate", job.bankAnswerDate()); 1541 switch (job.bankAnswerState()) { 1542 case eMyMoney::OnlineJob::sendingState::acceptedByBank: query.bindValue(":state", QLatin1String("acceptedByBank")); break; 1543 case eMyMoney::OnlineJob::sendingState::rejectedByBank: query.bindValue(":state", QLatin1String("rejectedByBank")); break; 1544 case eMyMoney::OnlineJob::sendingState::abortedByUser: query.bindValue(":state", QLatin1String("abortedByUser")); break; 1545 case eMyMoney::OnlineJob::sendingState::sendingError: query.bindValue(":state", QLatin1String("sendingError")); break; 1546 case eMyMoney::OnlineJob::sendingState::noBankAnswer: 1547 default: query.bindValue(":state", QLatin1String("noBankAnswer")); 1548 } 1549 query.bindValue(":locked", QVariant::fromValue<QString>(job.isLocked() ? QLatin1String("Y") : QLatin1String("N"))); 1550 } 1551 writePayeeIdentifier(const payeeIdentifier & pid,QSqlQuery & query)1552 void writePayeeIdentifier(const payeeIdentifier& pid, QSqlQuery& query) 1553 { 1554 query.bindValue(":id", pid.idString()); 1555 query.bindValue(":type", pid.iid()); 1556 if (!query.exec()) { // krazy:exclude=crashy 1557 qWarning() << buildError(query, Q_FUNC_INFO, QString("modifying payeeIdentifier")); 1558 throw MYMONEYEXCEPTIONSQL("modifying payeeIdentifier"); // krazy:exclude=crashy 1559 } 1560 } 1561 /** @} */ 1562 1563 /** 1564 * @name readMethods 1565 * @{ 1566 */ readFileInfo()1567 void readFileInfo() 1568 { 1569 Q_Q(MyMoneyStorageSql); 1570 signalProgress(0, 1, QObject::tr("Loading file information...")); 1571 1572 QSqlQuery query(*q); 1573 1574 query.prepare( 1575 "SELECT " 1576 " created, lastModified, " 1577 " encryptData, logonUser, logonAt, " 1578 " (SELECT count(*) FROM kmmInstitutions) AS institutions, " 1579 " (SELECT count(*) from kmmAccounts) AS accounts, " 1580 " (SELECT count(*) FROM kmmCurrencies) AS currencies, " 1581 " (SELECT count(*) FROM kmmPayees) AS payees, " 1582 " (SELECT count(*) FROM kmmTags) AS tags, " 1583 " (SELECT count(*) FROM kmmTransactions) AS transactions, " 1584 " (SELECT count(*) FROM kmmSplits) AS splits, " 1585 " (SELECT count(*) FROM kmmSecurities) AS securities, " 1586 " (SELECT count(*) FROM kmmCurrencies) AS currencies, " 1587 " (SELECT count(*) FROM kmmSchedules) AS schedules, " 1588 " (SELECT count(*) FROM kmmPrices) AS prices, " 1589 " (SELECT count(*) FROM kmmKeyValuePairs) AS kvps, " 1590 " (SELECT count(*) FROM kmmReportConfig) AS reports, " 1591 " (SELECT count(*) FROM kmmBudgetConfig) AS budgets, " 1592 " (SELECT count(*) FROM kmmOnlineJobs) AS onlineJobs, " 1593 " (SELECT count(*) FROM kmmPayeeIdentifier) AS payeeIdentifier " 1594 "FROM kmmFileInfo;" 1595 ); 1596 1597 if (!query.exec()) 1598 throw MYMONEYEXCEPTIONSQL("reading FileInfo"); // krazy:exclude=crashy 1599 if (!query.next()) 1600 throw MYMONEYEXCEPTIONSQL("retrieving FileInfo"); 1601 1602 QSqlRecord rec = query.record(); 1603 m_storage->setCreationDate(GETDATE(rec.indexOf("created"))); 1604 m_storage->setLastModificationDate(GETDATE(rec.indexOf("lastModified"))); 1605 1606 m_institutions = (ulong) GETULL(rec.indexOf("institutions")); 1607 m_accounts = (ulong) GETULL(rec.indexOf("accounts")); 1608 m_payees = (ulong) GETULL(rec.indexOf("payees")); 1609 m_tags = (ulong) GETULL(rec.indexOf("tags")); 1610 m_transactions = (ulong) GETULL(rec.indexOf("transactions")); 1611 m_splits = (ulong) GETULL(rec.indexOf("splits")); 1612 m_securities = (ulong) GETULL(rec.indexOf("securities")); 1613 m_currencies = (ulong) GETULL(rec.indexOf("currencies")); 1614 m_schedules = (ulong) GETULL(rec.indexOf("schedules")); 1615 m_prices = (ulong) GETULL(rec.indexOf("prices")); 1616 m_kvps = (ulong) GETULL(rec.indexOf("kvps")); 1617 m_reports = (ulong) GETULL(rec.indexOf("reports")); 1618 m_budgets = (ulong) GETULL(rec.indexOf("budgets")); 1619 m_onlineJobs = (ulong) GETULL(rec.indexOf("onlineJobs")); 1620 m_payeeIdentifier = (ulong) GETULL(rec.indexOf("payeeIdentifier")); 1621 1622 m_encryptData = GETSTRING(rec.indexOf("encryptData")); 1623 m_logonUser = GETSTRING(rec.indexOf("logonUser")); 1624 m_logonAt = GETDATETIME(rec.indexOf("logonAt")); 1625 1626 signalProgress(1, 0); 1627 m_storage->setPairs(readKeyValuePairs("STORAGE", QString("")).pairs()); 1628 } 1629 1630 void readLogonData(); 1631 void readUserInformation(); 1632 readInstitutions()1633 void readInstitutions() 1634 { 1635 Q_Q(MyMoneyStorageSql); 1636 try { 1637 QMap<QString, MyMoneyInstitution> iList = q->fetchInstitutions(); 1638 m_storage->loadInstitutions(iList); 1639 readFileInfo(); 1640 } catch (const MyMoneyException &) { 1641 throw; 1642 } 1643 } 1644 readAccounts()1645 void readAccounts() 1646 { 1647 Q_Q(MyMoneyStorageSql); 1648 m_storage->loadAccounts(q->fetchAccounts()); 1649 } 1650 readTransactions(const QString & tidList,const QString & dateClause)1651 void readTransactions(const QString& tidList, const QString& dateClause) 1652 { 1653 Q_Q(MyMoneyStorageSql); 1654 try { 1655 m_storage->loadTransactions(q->fetchTransactions(tidList, dateClause)); 1656 } catch (const MyMoneyException &) { 1657 throw; 1658 } 1659 } 1660 readTransactions()1661 void readTransactions() 1662 { 1663 readTransactions(QString(), QString()); 1664 } 1665 readSplit(const QSqlQuery & query)1666 MyMoneySplit readSplit(const QSqlQuery& query) const 1667 { 1668 Q_Q(const MyMoneyStorageSql); 1669 // Set these up as statics, since the field numbers should not change 1670 // during execution. 1671 static const MyMoneyDbTable& t = m_db.m_tables["kmmSplits"]; 1672 static const int splitIdCol = t.fieldNumber("splitId"); 1673 static const int transactionIdCol = t.fieldNumber("transactionId"); 1674 static const int payeeIdCol = t.fieldNumber("payeeId"); 1675 static const int reconcileDateCol = t.fieldNumber("reconcileDate"); 1676 static const int actionCol = t.fieldNumber("action"); 1677 static const int reconcileFlagCol = t.fieldNumber("reconcileFlag"); 1678 static const int valueCol = t.fieldNumber("value"); 1679 static const int sharesCol = t.fieldNumber("shares"); 1680 static const int priceCol = t.fieldNumber("price"); 1681 static const int memoCol = t.fieldNumber("memo"); 1682 static const int accountIdCol = t.fieldNumber("accountId"); 1683 static const int costCenterIdCol = t.fieldNumber("costCenterId"); 1684 static const int checkNumberCol = t.fieldNumber("checkNumber"); 1685 // static const int postDateCol = t.fieldNumber("postDate"); // FIXME - when Tom puts date into split object 1686 static const int bankIdCol = t.fieldNumber("bankId"); 1687 1688 MyMoneySplit s; 1689 1690 QList<QString> tagIdList; 1691 QSqlQuery query1(*const_cast <MyMoneyStorageSql*>(q)); 1692 query1.prepare("SELECT tagId from kmmTagSplits where splitId = :id and transactionId = :transactionId"); 1693 query1.bindValue(":id", GETSTRING(splitIdCol)); 1694 query1.bindValue(":transactionId", GETSTRING(transactionIdCol)); 1695 if (!query1.exec()) throw MYMONEYEXCEPTIONSQL("reading tagId in Split"); // krazy:exclude=crashy 1696 while (query1.next()) 1697 tagIdList << query1.value(0).toString(); 1698 1699 s.setTagIdList(tagIdList); 1700 s.setPayeeId(GETSTRING(payeeIdCol)); 1701 s.setReconcileDate(GETDATE(reconcileDateCol)); 1702 s.setAction(GETSTRING(actionCol)); 1703 s.setReconcileFlag(static_cast<Split::State>(GETINT(reconcileFlagCol))); 1704 s.setValue(MyMoneyMoney(MyMoneyUtils::QStringEmpty(GETSTRING(valueCol)))); 1705 s.setShares(MyMoneyMoney(MyMoneyUtils::QStringEmpty(GETSTRING(sharesCol)))); 1706 s.setPrice(MyMoneyMoney(MyMoneyUtils::QStringEmpty(GETSTRING(priceCol)))); 1707 s.setMemo(GETSTRING(memoCol)); 1708 s.setAccountId(GETSTRING(accountIdCol)); 1709 s.setCostCenterId(GETSTRING(costCenterIdCol)); 1710 s.setNumber(GETSTRING(checkNumberCol)); 1711 //s.setPostDate(GETDATETIME(postDateCol)); // FIXME - when Tom puts date into split object 1712 s.setBankID(GETSTRING(bankIdCol)); 1713 1714 return s; 1715 } 1716 readKeyValuePairs(const QString & kvpType,const QString & kvpId)1717 const MyMoneyKeyValueContainer readKeyValuePairs(const QString& kvpType, const QString& kvpId) const 1718 { 1719 Q_Q(const MyMoneyStorageSql); 1720 MyMoneyKeyValueContainer list; 1721 QSqlQuery query(*const_cast <MyMoneyStorageSql*>(q)); 1722 query.prepare("SELECT kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type and kvpId = :id;"); 1723 query.bindValue(":type", kvpType); 1724 query.bindValue(":id", kvpId); 1725 if (!query.exec()) throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("reading Kvp for %1 %2").arg(kvpType) // krazy:exclude=crashy 1726 .arg(kvpId)); 1727 while (query.next()) list.setValue(query.value(0).toString(), query.value(1).toString()); 1728 return (list); 1729 } 1730 readKeyValuePairs(const QString & kvpType,const QStringList & kvpIdList)1731 const QHash<QString, MyMoneyKeyValueContainer> readKeyValuePairs(const QString& kvpType, const QStringList& kvpIdList) const 1732 { 1733 Q_Q(const MyMoneyStorageSql); 1734 QHash<QString, MyMoneyKeyValueContainer> retval; 1735 1736 QSqlQuery query(*const_cast <MyMoneyStorageSql*>(q)); 1737 1738 QString idList; 1739 if (!kvpIdList.empty()) { 1740 idList = QString(" and kvpId IN ('%1')").arg(kvpIdList.join("', '")); 1741 } 1742 1743 QString sQuery = QString("SELECT kvpId, kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type %1 order by kvpId;").arg(idList); 1744 1745 query.prepare(sQuery); 1746 query.bindValue(":type", kvpType); 1747 if (!query.exec()) throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("reading Kvp List for %1").arg(kvpType)); // krazy:exclude=crashy 1748 1749 // Reserve enough space for all values. 1750 retval.reserve(kvpIdList.size()); 1751 1752 // The loop below is designed to limit the number of calls to 1753 // QHash::operator[] in order to speed up calls to this function. This 1754 // assumes that QString::operator== is faster. 1755 /* 1756 if (q.next()) { 1757 QString oldkey = q.value(0).toString(); 1758 MyMoneyKeyValueContainer& kvpc = retval[oldkey]; 1759 1760 kvpc.setValue(q.value(1).toString(), q.value(2).toString()); 1761 1762 while (q.next()) { 1763 if (q.value(0).toString() != oldkey) { 1764 oldkey = q.value(0).toString(); 1765 kvpc = retval[oldkey]; 1766 } 1767 kvpc.setValue(q.value(1).toString(), q.value(2).toString()); 1768 } 1769 } 1770 */ 1771 const bool isOnlineBanking = kvpType.toLower().compare(QLatin1String("onlinebanking")) == 0; 1772 while (query.next()) { 1773 QString kvpId = query.value(0).toString(); 1774 QString kvpKey = query.value(1).toString(); 1775 QString kvpData = query.value(2).toString(); 1776 if (isOnlineBanking) { 1777 if ((kvpKey.toLower().compare(QLatin1String("provider")) == 0) 1778 && (kvpData.toLower().compare(QLatin1String("kmymoney ofx")) == 0)) { 1779 kvpData = QStringLiteral("ofximporter"); 1780 } 1781 } 1782 retval[kvpId].setValue(kvpKey, kvpData); 1783 } 1784 return (retval); 1785 } 1786 readSchedules()1787 void readSchedules() 1788 { 1789 Q_Q(MyMoneyStorageSql); 1790 try { 1791 m_storage->loadSchedules(q->fetchSchedules()); 1792 } catch (const MyMoneyException &) { 1793 throw; 1794 } 1795 1796 } 1797 readSecurities()1798 void readSecurities() 1799 { 1800 Q_Q(MyMoneyStorageSql); 1801 try { 1802 m_storage->loadSecurities(q->fetchSecurities()); 1803 } catch (const MyMoneyException &) { 1804 throw; 1805 } 1806 1807 } 1808 readPrices()1809 void readPrices() 1810 { 1811 Q_Q(MyMoneyStorageSql); 1812 try { 1813 m_storage->loadPrices(q->fetchPrices()); 1814 } catch (const MyMoneyException &) { 1815 throw; 1816 } 1817 } 1818 readCurrencies()1819 void readCurrencies() 1820 { 1821 Q_Q(MyMoneyStorageSql); 1822 try { 1823 m_storage->loadCurrencies(q->fetchCurrencies()); 1824 } catch (const MyMoneyException &) { 1825 throw; 1826 } 1827 } 1828 readReports()1829 void readReports() 1830 { 1831 Q_Q(MyMoneyStorageSql); 1832 try { 1833 m_storage->loadReports(q->fetchReports()); 1834 } catch (const MyMoneyException &) { 1835 throw; 1836 } 1837 } 1838 readBudgets()1839 void readBudgets() 1840 { 1841 Q_Q(MyMoneyStorageSql); 1842 m_storage->loadBudgets(q->fetchBudgets()); 1843 } 1844 readOnlineJobs()1845 void readOnlineJobs() 1846 { 1847 Q_Q(MyMoneyStorageSql); 1848 m_storage->loadOnlineJobs(q->fetchOnlineJobs()); 1849 } 1850 1851 /** @} */ 1852 deleteTransaction(const QString & id)1853 void deleteTransaction(const QString& id) 1854 { 1855 Q_Q(MyMoneyStorageSql); 1856 MyMoneyDbTransaction t(*q, Q_FUNC_INFO); 1857 QSqlQuery query(*q); 1858 QVariantList idList; 1859 idList << id; 1860 query.prepare("DELETE FROM kmmSplits WHERE transactionId = :transactionId;"); 1861 query.bindValue(":transactionId", idList); 1862 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Splits"); 1863 1864 query.prepare("DELETE FROM kmmKeyValuePairs WHERE kvpType = 'SPLIT' " 1865 "AND kvpId LIKE '?%'"); 1866 query.bindValue(1, idList); 1867 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting Splits KVP"); 1868 1869 m_splits -= query.numRowsAffected(); 1870 deleteKeyValuePairs("TRANSACTION", idList); 1871 query.prepare(m_db.m_tables["kmmTransactions"].deleteString()); 1872 query.bindValue(":id", idList); 1873 if (!query.execBatch()) 1874 throw MYMONEYEXCEPTIONSQL("deleting Transaction"); 1875 } 1876 deleteTagSplitsList(const QString & txId,const QList<int> & splitIdList)1877 void deleteTagSplitsList(const QString& txId, const QList<int>& splitIdList) 1878 { 1879 Q_Q(MyMoneyStorageSql); 1880 MyMoneyDbTransaction t(*q, Q_FUNC_INFO); 1881 QVariantList iList; 1882 QVariantList transactionIdList; 1883 1884 // qCopy segfaults here, so do it with a hand-rolled loop 1885 foreach (int it_s, splitIdList) { 1886 iList << it_s; 1887 transactionIdList << txId; 1888 } 1889 QSqlQuery query(*q); 1890 query.prepare("DELETE FROM kmmTagSplits WHERE transactionId = :transactionId AND splitId = :splitId"); 1891 query.bindValue(":splitId", iList); 1892 query.bindValue(":transactionId", transactionIdList); 1893 if (!query.execBatch()) throw MYMONEYEXCEPTIONSQL("deleting tagSplits"); 1894 } 1895 deleteSchedule(const QString & id)1896 void deleteSchedule(const QString& id) 1897 { 1898 Q_Q(MyMoneyStorageSql); 1899 deleteTransaction(id); 1900 QSqlQuery query(*q); 1901 query.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id"); 1902 query.bindValue(":id", id); 1903 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("deleting Schedule Payment History"); // krazy:exclude=crashy 1904 query.prepare(m_db.m_tables["kmmSchedules"].deleteString()); 1905 query.bindValue(":id", id); 1906 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("deleting Schedule"); // krazy:exclude=crashy 1907 //FIXME: enable when schedules have KVPs. 1908 //deleteKeyValuePairs("SCHEDULE", id); 1909 } 1910 deleteKeyValuePairs(const QString & kvpType,const QVariantList & idList)1911 void deleteKeyValuePairs(const QString& kvpType, const QVariantList& idList) 1912 { 1913 Q_Q(MyMoneyStorageSql); 1914 QSqlQuery query(*q); 1915 query.prepare("DELETE FROM kmmKeyValuePairs WHERE kvpType = :kvpType AND kvpId = :kvpId;"); 1916 QVariantList typeList; 1917 for (int i = 0; i < idList.size(); ++i) { 1918 typeList << kvpType; 1919 } 1920 query.bindValue(":kvpType", typeList); 1921 query.bindValue(":kvpId", idList); 1922 if (!query.execBatch()) { 1923 QString idString; 1924 for (int i = 0; i < idList.size(); ++i) { 1925 idString.append(idList[i].toString() + ' '); 1926 } 1927 throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("deleting kvp for %1 %2").arg(kvpType).arg(idString)); 1928 } 1929 m_kvps -= query.numRowsAffected(); 1930 } 1931 calcHighId(ulong i,const QString & id)1932 ulong calcHighId(ulong i, const QString& id) 1933 { 1934 QString nid = id; 1935 ulong high = (ulong) nid.remove(QRegExp("[A-Z]*")).toULongLong(); 1936 return std::max(high, i); 1937 } 1938 1939 void setVersion(const QString& version); 1940 splitState(const TransactionFilter::State & state)1941 int splitState(const TransactionFilter::State& state) const 1942 { 1943 auto rc = (int)Split::State::NotReconciled; 1944 1945 switch (state) { 1946 default: 1947 case TransactionFilter::State::NotReconciled: 1948 break; 1949 1950 case TransactionFilter::State::Cleared: 1951 rc = (int)Split::State::Cleared; 1952 break; 1953 1954 case TransactionFilter::State::Reconciled: 1955 rc = (int)Split::State::Reconciled; 1956 break; 1957 1958 case TransactionFilter::State::Frozen: 1959 rc = (int)Split::State::Frozen; 1960 break; 1961 } 1962 return rc; 1963 } 1964 getDate(const QString & date)1965 QDate getDate(const QString& date) const 1966 { 1967 return (date.isNull() ? QDate() : QDate::fromString(date, Qt::ISODate)); 1968 } 1969 getDateTime(const QString & date)1970 QDateTime getDateTime(const QString& date) const 1971 { 1972 return (date.isNull() ? QDateTime() : QDateTime::fromString(date, Qt::ISODate)); 1973 } 1974 fileExists(const QString & dbName)1975 bool fileExists(const QString& dbName) 1976 { 1977 QFile f(dbName); 1978 if (!f.exists()) { 1979 m_error = i18n("SQLite file %1 does not exist", dbName); 1980 return (false); 1981 } 1982 return (true); 1983 } 1984 1985 /** @brief a function to build a comprehensive error message for an SQL error */ buildError(const QSqlQuery & query,const QString & function,const QString & messageb)1986 QString& buildError(const QSqlQuery& query, const QString& function, 1987 const QString& messageb) const 1988 { 1989 Q_Q(const MyMoneyStorageSql); 1990 return (buildError(query, function, messageb, q)); 1991 } 1992 buildError(const QSqlQuery & query,const QString & function,const QString & message,const QSqlDatabase * db)1993 QString& buildError(const QSqlQuery& query, const QString& function, 1994 const QString& message, const QSqlDatabase* db) const 1995 { 1996 Q_Q(const MyMoneyStorageSql); 1997 QString s = QString("Error in function %1 : %2").arg(function).arg(message); 1998 s += QString("\nDriver = %1, Host = %2, User = %3, Database = %4") 1999 .arg(db->driverName()).arg(db->hostName()).arg(db->userName()).arg(db->databaseName()); 2000 QSqlError e = db->lastError(); 2001 s += QString("\nDriver Error: %1").arg(e.driverText()); 2002 s += QString("\nDatabase Error No %1: %2").arg(e.number()).arg(e.databaseText()); 2003 s += QString("\nText: %1").arg(e.text()); 2004 s += QString("\nError type %1").arg(e.type()); 2005 e = query.lastError(); 2006 s += QString("\nExecuted: %1").arg(query.executedQuery()); 2007 s += QString("\nQuery error No %1: %2").arg(e.number()).arg(e.text()); 2008 s += QString("\nError type %1").arg(e.type()); 2009 2010 const_cast <MyMoneyStorageSql*>(q)->d_func()->m_error = s; 2011 qDebug("%s", qPrintable(s)); 2012 const_cast <MyMoneyStorageSql*>(q)->cancelCommitUnit(function); 2013 return (const_cast <MyMoneyStorageSql*>(q)->d_func()->m_error); 2014 } 2015 2016 /** 2017 * MyMoneyStorageSql create database 2018 * 2019 * @param url pseudo-URL of database to be opened 2020 * 2021 * @return true - creation successful 2022 * @return false - could not create 2023 * 2024 */ 2025 createDatabase(const QUrl & url)2026 bool createDatabase(const QUrl &url) 2027 { 2028 Q_Q(MyMoneyStorageSql); 2029 int rc = true; 2030 if (!m_driver->requiresCreation()) return(true); // not needed for sqlite 2031 QString dbName = url.path().right(url.path().length() - 1); // remove separator slash 2032 if (!m_driver->canAutocreate()) { 2033 m_error = i18n("Automatic database creation for type %1 is not currently implemented.\n" 2034 "Please create database %2 manually", q->driverName(), dbName); 2035 return (false); 2036 } 2037 // create the database (only works for mysql and postgre at present) 2038 { // for this code block, see QSqlDatabase API re removeDatabase 2039 QSqlDatabase maindb = QSqlDatabase::addDatabase(q->driverName(), "main"); 2040 maindb.setDatabaseName(m_driver->defaultDbName()); 2041 maindb.setHostName(url.host()); 2042 maindb.setUserName(url.userName()); 2043 maindb.setPassword(url.password()); 2044 if (!maindb.open()) { 2045 throw MYMONEYEXCEPTION(QString::fromLatin1("opening database %1 in function %2") 2046 .arg(maindb.databaseName()).arg(Q_FUNC_INFO)); 2047 } else { 2048 QSqlQuery qm(maindb); 2049 qm.exec(QString::fromLatin1("PRAGMA key = '%1'").arg(q->password())); 2050 QString qs = m_driver->createDbString(dbName) + ';'; 2051 if (!qm.exec(qs)) { // krazy:exclude=crashy 2052 buildError(qm, Q_FUNC_INFO, 2053 i18n("Error in create database %1; do you have create permissions?", dbName), &maindb); 2054 rc = false; 2055 } 2056 maindb.close(); 2057 } 2058 } 2059 QSqlDatabase::removeDatabase("main"); 2060 return (rc); 2061 } 2062 2063 upgradeDb()2064 int upgradeDb() 2065 { 2066 Q_Q(MyMoneyStorageSql); 2067 //signalProgress(0, 1, QObject::tr("Upgrading database...")); 2068 QSqlQuery query(*q); 2069 query.prepare("SELECT version FROM kmmFileInfo;"); 2070 if (!query.exec() || !query.next()) { // krazy:exclude=crashy 2071 if (!m_newDatabase) { 2072 buildError(query, Q_FUNC_INFO, "Error retrieving file info (version)"); 2073 return(1); 2074 } else { 2075 m_dbVersion = m_db.currentVersion(); 2076 m_storage->setFileFixVersion(m_storage->currentFixVersion()); 2077 QSqlQuery query2(*q); 2078 query2.prepare("UPDATE kmmFileInfo SET version = :version, \ 2079 fixLevel = :fixLevel;"); 2080 query2.bindValue(":version", m_dbVersion); 2081 query2.bindValue(":fixLevel", m_storage->currentFixVersion()); 2082 if (!query2.exec()) { // krazy:exclude=crashy 2083 buildError(query2, Q_FUNC_INFO, "Error updating file info(version)"); 2084 return(1); 2085 } 2086 return (0); 2087 } 2088 } 2089 // prior to dbv6, 'version' format was 'dbversion.fixLevel+1' 2090 // as of dbv6, these are separate fields 2091 QString version = query.value(0).toString(); 2092 if (version.contains('.')) { 2093 m_dbVersion = query.value(0).toString().section('.', 0, 0).toUInt(); 2094 m_storage->setFileFixVersion(query.value(0).toString().section('.', 1, 1).toUInt() - 1); 2095 } else { 2096 m_dbVersion = version.toUInt(); 2097 query.prepare("SELECT fixLevel FROM kmmFileInfo;"); 2098 if (!query.exec() || !query.next()) { // krazy:exclude=crashy 2099 buildError(query, Q_FUNC_INFO, "Error retrieving file info (fixLevel)"); 2100 return(1); 2101 } 2102 m_storage->setFileFixVersion(query.value(0).toUInt()); 2103 } 2104 2105 if (m_dbVersion == m_db.currentVersion()) 2106 return 0; 2107 2108 int rc = 0; 2109 2110 // Drop VIEWs 2111 QStringList lowerTables = tables(QSql::AllTables); 2112 for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) { 2113 (*i) = (*i).toLower(); 2114 } 2115 2116 for (QMap<QString, MyMoneyDbView>::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) { 2117 if (lowerTables.contains(tt.key().toLower())) { 2118 if (!query.exec("DROP VIEW " + tt.value().name() + ';')) // krazy:exclude=crashy 2119 throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("dropping view %1").arg(tt.key())); 2120 } 2121 } 2122 2123 while ((m_dbVersion < m_db.currentVersion()) && (rc == 0)) { 2124 qDebug() << "Perform upgrade to db layout version" << m_dbVersion+1; 2125 query.finish(); 2126 switch (m_dbVersion) { 2127 case 0: 2128 if ((rc = upgradeToV1()) != 0) return (1); 2129 ++m_dbVersion; 2130 break; 2131 case 1: 2132 if ((rc = upgradeToV2()) != 0) return (1); 2133 ++m_dbVersion; 2134 break; 2135 case 2: 2136 if ((rc = upgradeToV3()) != 0) return (1); 2137 ++m_dbVersion; 2138 break; 2139 case 3: 2140 if ((rc = upgradeToV4()) != 0) return (1); 2141 ++m_dbVersion; 2142 break; 2143 case 4: 2144 if ((rc = upgradeToV5()) != 0) return (1); 2145 ++m_dbVersion; 2146 break; 2147 case 5: 2148 if ((rc = upgradeToV6()) != 0) return (1); 2149 ++m_dbVersion; 2150 break; 2151 case 6: 2152 if ((rc = upgradeToV7()) != 0) return (1); 2153 ++m_dbVersion; 2154 break; 2155 case 7: 2156 if ((rc = upgradeToV8()) != 0) return (1); 2157 ++m_dbVersion; 2158 break; 2159 case 8: 2160 if ((rc = upgradeToV9()) != 0) return (1); 2161 ++m_dbVersion; 2162 break; 2163 case 9: 2164 if ((rc = upgradeToV10()) != 0) return (1); 2165 ++m_dbVersion; 2166 break; 2167 case 10: 2168 if ((rc = upgradeToV11()) != 0) return (1); 2169 ++m_dbVersion; 2170 break; 2171 case 11: 2172 if ((rc = upgradeToV12()) != 0) return (1); 2173 ++m_dbVersion; 2174 break; 2175 default: 2176 qWarning("Unknown version number in database - %d", m_dbVersion); 2177 } 2178 } 2179 query.finish(); 2180 2181 // restore VIEWs 2182 lowerTables = tables(QSql::AllTables); 2183 for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) { 2184 (*i) = (*i).toLower(); 2185 } 2186 2187 for (QMap<QString, MyMoneyDbView>::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) { 2188 if (!lowerTables.contains(tt.key().toLower())) { 2189 if (!query.exec(tt.value().createString())) // krazy:exclude=crashy 2190 throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("creating view %1").arg(tt.key())); 2191 } 2192 } 2193 2194 // write updated version to DB 2195 //setVersion(QString("%1.%2").arg(m_dbVersion).arg(m_minorVersion)) 2196 query.prepare(QString("UPDATE kmmFileInfo SET version = :version;")); 2197 query.bindValue(":version", m_dbVersion); 2198 if (!query.exec()) { // krazy:exclude=crashy 2199 buildError(query, Q_FUNC_INFO, "Error updating db version"); 2200 return (1); 2201 } 2202 //signalProgress(-1,-1); 2203 return (0); 2204 } 2205 upgradeToV1()2206 int upgradeToV1() 2207 { 2208 Q_Q(MyMoneyStorageSql); 2209 MyMoneyDbTransaction t(*q, Q_FUNC_INFO); 2210 QSqlQuery query(*q); 2211 // change kmmSplits pkey to (transactionId, splitId) 2212 if (!query.exec("ALTER TABLE kmmSplits ADD PRIMARY KEY (transactionId, splitId);")) { // krazy:exclude=crashy 2213 buildError(query, Q_FUNC_INFO, "Error updating kmmSplits pkey"); 2214 return (1); 2215 } 2216 // change kmmSplits alter checkNumber varchar(32) 2217 if (!query.exec(m_db.m_tables["kmmSplits"].modifyColumnString(m_driver, "checkNumber", // krazy:exclude=crashy 2218 MyMoneyDbColumn("checkNumber", "varchar(32)")))) { 2219 buildError(query, Q_FUNC_INFO, "Error expanding kmmSplits.checkNumber"); 2220 return (1); 2221 } 2222 // change kmmSplits add postDate datetime 2223 if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) 2224 return (1); 2225 // initialize it to same value as transaction (do it the long way round) 2226 query.prepare("SELECT id, postDate FROM kmmTransactions WHERE txType = 'N';"); 2227 if (!query.exec()) { // krazy:exclude=crashy 2228 buildError(query, Q_FUNC_INFO, "Error priming kmmSplits.postDate"); 2229 return (1); 2230 } 2231 QMap<QString, QDateTime> tids; 2232 while (query.next()) tids[query.value(0).toString()] = query.value(1).toDateTime(); 2233 QMap<QString, QDateTime>::ConstIterator it; 2234 for (it = tids.constBegin(); it != tids.constEnd(); ++it) { 2235 query.prepare("UPDATE kmmSplits SET postDate=:postDate WHERE transactionId = :id;"); 2236 query.bindValue(":postDate", it.value().toString(Qt::ISODate)); 2237 query.bindValue(":id", it.key()); 2238 if (!query.exec()) { // krazy:exclude=crashy 2239 buildError(query, Q_FUNC_INFO, "priming kmmSplits.postDate"); 2240 return(1); 2241 } 2242 } 2243 // add index to kmmKeyValuePairs to (kvpType,kvpId) 2244 QStringList list; 2245 list << "kvpType" << "kvpId"; 2246 if (!query.exec(MyMoneyDbIndex("kmmKeyValuePairs", "kmmKVPtype_id", list, false).generateDDL(m_driver) + ';')) { 2247 buildError(query, Q_FUNC_INFO, "Error adding kmmKeyValuePairs index"); 2248 return (1); 2249 } 2250 // add index to kmmSplits to (accountId, txType) 2251 list.clear(); 2252 list << "accountId" << "txType"; 2253 if (!query.exec(MyMoneyDbIndex("kmmSplits", "kmmSplitsaccount_type", list, false).generateDDL(m_driver) + ';')) { 2254 buildError(query, Q_FUNC_INFO, "Error adding kmmSplits index"); 2255 return (1); 2256 } 2257 // change kmmSchedulePaymentHistory pkey to (schedId, payDate) 2258 if (!query.exec("ALTER TABLE kmmSchedulePaymentHistory ADD PRIMARY KEY (schedId, payDate);")) { 2259 buildError(query, Q_FUNC_INFO, "Error updating kmmSchedulePaymentHistory pkey"); 2260 return (1); 2261 } 2262 // change kmmPrices pkey to (fromId, toId, priceDate) 2263 if (!query.exec("ALTER TABLE kmmPrices ADD PRIMARY KEY (fromId, toId, priceDate);")) { 2264 buildError(query, Q_FUNC_INFO, "Error updating kmmPrices pkey"); 2265 return (1); 2266 } 2267 // change kmmReportConfig pkey to (name) 2268 // There wasn't one previously, so no need to drop it. 2269 if (!query.exec("ALTER TABLE kmmReportConfig ADD PRIMARY KEY (name);")) { 2270 buildError(query, Q_FUNC_INFO, "Error updating kmmReportConfig pkey"); 2271 return (1); 2272 } 2273 // change kmmFileInfo add budgets, hiBudgetId unsigned bigint 2274 // change kmmFileInfo add logonUser 2275 // change kmmFileInfo add logonAt datetime 2276 if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) 2277 return (1); 2278 // change kmmAccounts add transactionCount unsigned bigint as last field 2279 if (!alterTable(m_db.m_tables["kmmAccounts"], m_dbVersion)) 2280 return (1); 2281 // calculate the transaction counts. the application logic defines an account's tx count 2282 // in such a way as to count multiple splits in a tx which reference the same account as one. 2283 // this is the only way I can think of to do this which will work in sqlite too. 2284 // inefficient, but it only gets done once... 2285 // get a list of all accounts so we'll get a zero value for those without txs 2286 query.prepare("SELECT id FROM kmmAccounts"); 2287 if (!query.exec()) { // krazy:exclude=crashy 2288 buildError(query, Q_FUNC_INFO, "Error retrieving accounts for transaction counting"); 2289 return(1); 2290 } 2291 while (query.next()) { 2292 m_transactionCountMap[query.value(0).toString()] = 0; 2293 } 2294 query.prepare("SELECT accountId, transactionId FROM kmmSplits WHERE txType = 'N' ORDER BY 1, 2"); 2295 if (!query.exec()) { // krazy:exclude=crashy 2296 buildError(query, Q_FUNC_INFO, "Error retrieving splits for transaction counting"); 2297 return(1); 2298 } 2299 QString lastAcc, lastTx; 2300 while (query.next()) { 2301 QString thisAcc = query.value(0).toString(); 2302 QString thisTx = query.value(1).toString(); 2303 if ((thisAcc != lastAcc) || (thisTx != lastTx)) ++m_transactionCountMap[thisAcc]; 2304 lastAcc = thisAcc; 2305 lastTx = thisTx; 2306 } 2307 QHash<QString, ulong>::ConstIterator itm; 2308 query.prepare("UPDATE kmmAccounts SET transactionCount = :txCount WHERE id = :id;"); 2309 for (itm = m_transactionCountMap.constBegin(); itm != m_transactionCountMap.constEnd(); ++itm) { 2310 query.bindValue(":txCount", QString::number(itm.value())); 2311 query.bindValue(":id", itm.key()); 2312 if (!query.exec()) { // krazy:exclude=crashy 2313 buildError(query, Q_FUNC_INFO, "Error updating transaction count"); 2314 return (1); 2315 } 2316 } 2317 m_transactionCountMap.clear(); 2318 return (0); 2319 } 2320 upgradeToV2()2321 int upgradeToV2() 2322 { 2323 Q_Q(MyMoneyStorageSql); 2324 MyMoneyDbTransaction t(*q, Q_FUNC_INFO); 2325 // change kmmSplits add price, priceFormatted fields 2326 if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) 2327 return (1); 2328 return (0); 2329 } 2330 upgradeToV3()2331 int upgradeToV3() 2332 { 2333 Q_Q(MyMoneyStorageSql); 2334 MyMoneyDbTransaction t(*q, Q_FUNC_INFO); 2335 QSqlQuery query(*q); 2336 // kmmSchedules - add occurrenceMultiplier 2337 // The default value is given here to populate the column. 2338 if (!query.exec("ALTER TABLE kmmSchedules ADD COLUMN " + 2339 MyMoneyDbIntColumn("occurenceMultiplier", 2340 MyMoneyDbIntColumn::SMALL, false, false, true) 2341 .generateDDL(m_driver) + " DEFAULT 0;")) { 2342 buildError(query, Q_FUNC_INFO, "Error adding kmmSchedules.occurenceMultiplier"); 2343 return (1); 2344 } 2345 //The default is less than any useful value, so as each schedule is hit, it will update 2346 //itself to the appropriate value. 2347 return 0; 2348 } 2349 upgradeToV4()2350 int upgradeToV4() 2351 { 2352 Q_Q(MyMoneyStorageSql); 2353 MyMoneyDbTransaction t(*q, Q_FUNC_INFO); 2354 QSqlQuery query(*q); 2355 // kmmSplits - add index on transactionId + splitId 2356 QStringList list; 2357 list << "transactionId" << "splitId"; 2358 if (!query.exec(MyMoneyDbIndex("kmmSplits", "kmmTx_Split", list, false).generateDDL(m_driver) + ';')) { 2359 buildError(query, Q_FUNC_INFO, "Error adding kmmSplits index on (transactionId, splitId)"); 2360 return (1); 2361 } 2362 return 0; 2363 } 2364 upgradeToV5()2365 int upgradeToV5() 2366 { 2367 Q_Q(MyMoneyStorageSql); 2368 MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); 2369 // kmmSplits - add bankId 2370 if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) 2371 return (1); 2372 //kmmPayees - add columns "notes" "defaultAccountId" "matchData" "matchIgnoreCase" "matchKeys"; 2373 if (!alterTable(m_db.m_tables["kmmPayees"], m_dbVersion)) 2374 return (1); 2375 // kmmReportConfig - drop primary key on name since duplicate names are allowed 2376 if (!alterTable(m_db.m_tables["kmmReportConfig"], m_dbVersion)) 2377 return (1); 2378 //} 2379 return 0; 2380 } 2381 upgradeToV6()2382 int upgradeToV6() 2383 { 2384 Q_Q(MyMoneyStorageSql); 2385 q->startCommitUnit(Q_FUNC_INFO); 2386 QSqlQuery query(*q); 2387 // kmmFileInfo - add fixLevel 2388 if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) 2389 return (1); 2390 // upgrade Mysql to InnoDB transaction-safe engine 2391 // the following is not a good way to test for mysql - think of a better way 2392 if (!m_driver->tableOptionString().isEmpty()) { 2393 for (QMap<QString, MyMoneyDbTable>::ConstIterator tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) { 2394 if (!query.exec(QString("ALTER TABLE %1 ENGINE = InnoDB;").arg(tt.value().name()))) { 2395 buildError(query, Q_FUNC_INFO, "Error updating to InnoDB"); 2396 return (1); 2397 } 2398 } 2399 } 2400 2401 // the alterTable function really doesn't work too well 2402 // with adding a new column which is also to be primary key 2403 // so add the column first 2404 if (!query.exec("ALTER TABLE kmmReportConfig ADD COLUMN " + 2405 MyMoneyDbColumn("id", "varchar(32)").generateDDL(m_driver) + ';')) { 2406 buildError(query, Q_FUNC_INFO, "adding id to report table"); 2407 return(1); 2408 } 2409 QMap<QString, MyMoneyReport> reportList = q->fetchReports(); 2410 // the V5 database allowed lots of duplicate reports with no 2411 // way to distinguish between them. The fetchReports call 2412 // will have effectively removed all duplicates 2413 // so we now delete from the db and re-write them 2414 if (!query.exec("DELETE FROM kmmReportConfig;")) { 2415 buildError(query, Q_FUNC_INFO, "Error deleting reports"); 2416 return (1); 2417 } 2418 // add unique id to reports table 2419 if (!alterTable(m_db.m_tables["kmmReportConfig"], m_dbVersion)) 2420 return(1); 2421 2422 QMap<QString, MyMoneyReport>::const_iterator it_r; 2423 for (it_r = reportList.constBegin(); it_r != reportList.constEnd(); ++it_r) { 2424 MyMoneyReport r = *it_r; 2425 query.prepare(m_db.m_tables["kmmReportConfig"].insertString()); 2426 writeReport(*it_r, query); 2427 } 2428 2429 q->endCommitUnit(Q_FUNC_INFO); 2430 return 0; 2431 } 2432 upgradeToV7()2433 int upgradeToV7() 2434 { 2435 Q_Q(MyMoneyStorageSql); 2436 MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); 2437 2438 // add tags support 2439 // kmmFileInfo - add tags and hiTagId 2440 if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) 2441 return (1); 2442 2443 m_tags = 0; 2444 return 0; 2445 } 2446 upgradeToV8()2447 int upgradeToV8() 2448 { 2449 Q_Q(MyMoneyStorageSql); 2450 MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); 2451 2452 // Added onlineJobs and payeeIdentifier 2453 if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) 2454 return (1); 2455 2456 return 0; 2457 } 2458 upgradeToV9()2459 int upgradeToV9() 2460 { 2461 Q_Q(MyMoneyStorageSql); 2462 MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); 2463 2464 // kmmSplits - add bankId 2465 if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) 2466 return (1); 2467 2468 return 0; 2469 } 2470 upgradeToV10()2471 int upgradeToV10() 2472 { 2473 Q_Q(MyMoneyStorageSql); 2474 MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); 2475 2476 if (!alterTable(m_db.m_tables["kmmPayeesPayeeIdentifier"], m_dbVersion)) 2477 return (1); 2478 if (!alterTable(m_db.m_tables["kmmAccountsPayeeIdentifier"], m_dbVersion)) 2479 return (1); 2480 2481 return 0; 2482 } 2483 upgradeToV11()2484 int upgradeToV11() 2485 { 2486 Q_Q(MyMoneyStorageSql); 2487 MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); 2488 2489 // add column roundingMethodCol to kmmSecurities 2490 if (!alterTable(m_db.m_tables["kmmSecurities"], m_dbVersion)) 2491 return 1; 2492 // add column pricePrecision to kmmCurrencies 2493 if (!alterTable(m_db.m_tables["kmmCurrencies"], m_dbVersion)) 2494 return 1; 2495 2496 return 0; 2497 } 2498 upgradeToV12()2499 int upgradeToV12() 2500 { 2501 Q_Q(MyMoneyStorageSql); 2502 MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); 2503 2504 switch(haveColumnInTable(QLatin1String("kmmSchedules"), QLatin1String("lastDayInMonth"))) { 2505 case -1: 2506 return 1; 2507 case 1: // column exists, nothing to do 2508 break; 2509 case 0: // need update of kmmSchedules 2510 // add column lastDayInMonth. Simply redo the update for 10 .. 11 2511 if (!alterTable(m_db.m_tables["kmmSchedules"], m_dbVersion-1)) 2512 return 1; 2513 break; 2514 } 2515 2516 switch(haveColumnInTable(QLatin1String("kmmSecurities"), QLatin1String("roundingMethod"))) { 2517 case -1: 2518 return 1; 2519 case 1: // column exists, nothing to do 2520 break; 2521 case 0: // need update of kmmSecurities and kmmCurrencies 2522 // add column roundingMethodCol to kmmSecurities. Simply redo the update for 10 .. 11 2523 if (!alterTable(m_db.m_tables["kmmSecurities"], m_dbVersion-1)) 2524 return 1; 2525 // add column pricePrecision to kmmCurrencies. Simply redo the update for 10 .. 11 2526 if (!alterTable(m_db.m_tables["kmmCurrencies"], m_dbVersion-1)) 2527 return 1; 2528 break; 2529 } 2530 return 0; 2531 } 2532 createTables()2533 int createTables() 2534 { 2535 Q_Q(MyMoneyStorageSql); 2536 // check tables, create if required 2537 // convert everything to lower case, since SQL standard is case insensitive 2538 // table and column names (when not delimited), but some DBMSs disagree. 2539 QStringList lowerTables = tables(QSql::AllTables); 2540 for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) { 2541 (*i) = (*i).toLower(); 2542 } 2543 2544 for (QMap<QString, MyMoneyDbTable>::ConstIterator tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) { 2545 if (!lowerTables.contains(tt.key().toLower())) { 2546 createTable(tt.value()); 2547 } 2548 } 2549 2550 QSqlQuery query(*q); 2551 for (QMap<QString, MyMoneyDbView>::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) { 2552 if (!lowerTables.contains(tt.key().toLower())) { 2553 if (!query.exec(tt.value().createString())) throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("creating view %1").arg(tt.key())); 2554 } 2555 } 2556 2557 // The columns to store version info changed with version 6. Prior versions are not supported here but an error is prevented and 2558 // an old behaviour is used: call upgradeDb(). 2559 m_dbVersion = m_db.currentVersion(); 2560 if (m_dbVersion >= 6) { 2561 // create the fileinfo stuff if it does not exist 2562 query.prepare("SELECT count(*) FROM kmmFileInfo;"); 2563 if (!query.exec() || !query.next()) 2564 throw MYMONEYEXCEPTIONSQL("checking fileinfo"); // krazy:exclude=crashy 2565 2566 if (query.value(0).toInt() == 0) { 2567 query.prepare(QLatin1String("INSERT INTO kmmFileInfo (version, fixLevel) VALUES(?,?);")); 2568 query.bindValue(0, m_dbVersion); 2569 query.bindValue(1, m_storage->fileFixVersion()); 2570 if (!query.exec()) 2571 throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("Saving database version")); 2572 } 2573 } 2574 query.finish(); 2575 2576 return upgradeDb(); 2577 } 2578 2579 void createTable(const MyMoneyDbTable& t, int version = std::numeric_limits<int>::max()) 2580 { 2581 Q_Q(MyMoneyStorageSql); 2582 // create the tables 2583 QStringList ql = t.generateCreateSQL(m_driver, version).split('\n', QString::SkipEmptyParts); 2584 QSqlQuery query(*q); foreach(const QString & i,ql)2585 foreach (const QString& i, ql) { 2586 if (!query.exec(i)) throw MYMONEYEXCEPTIONSQL(QString::fromLatin1("creating table/index %1").arg(t.name())); 2587 } 2588 } 2589 alterTable(const MyMoneyDbTable & t,int fromVersion)2590 bool alterTable(const MyMoneyDbTable& t, int fromVersion) 2591 { 2592 Q_Q(MyMoneyStorageSql); 2593 const int toVersion = fromVersion + 1; 2594 2595 QString tempTableName = t.name(); 2596 tempTableName.replace("kmm", "kmmtmp"); 2597 QSqlQuery query(*q); 2598 // drop primary key if it has one (and driver supports it) 2599 if (t.hasPrimaryKey(fromVersion)) { 2600 QString dropString = m_driver->dropPrimaryKeyString(t.name()); 2601 if (!dropString.isEmpty()) { 2602 if (!query.exec(dropString)) { 2603 buildError(query, Q_FUNC_INFO, QString("Error dropping old primary key from %1").arg(t.name())); 2604 return false; 2605 } 2606 } 2607 } 2608 query.finish(); 2609 for (MyMoneyDbTable::index_iterator i = t.indexBegin(); i != t.indexEnd(); ++i) { 2610 QString indexName = t.name() + '_' + i->name() + "_idx"; 2611 if (!query.exec(m_driver->dropIndexString(t.name(), indexName))) { 2612 buildError(query, Q_FUNC_INFO, QString("Error dropping index from %1").arg(t.name())); 2613 return false; 2614 } 2615 query.finish(); 2616 } 2617 if (!query.exec(QString("ALTER TABLE " + t.name() + " RENAME TO " + tempTableName + ';'))) { 2618 buildError(query, Q_FUNC_INFO, QString("Error renaming table %1").arg(t.name())); 2619 return false; 2620 } 2621 query.finish(); 2622 createTable(t, toVersion); 2623 if (q->getRecCount(tempTableName) > 0) { 2624 query.finish(); 2625 query.prepare(QString("INSERT INTO " + t.name() + " (" + t.columnList(fromVersion, true) + 2626 ") SELECT " + t.columnList(fromVersion, false) + " FROM " + tempTableName + ';')); 2627 if (!query.exec()) { // krazy:exclude=crashy 2628 buildError(query, Q_FUNC_INFO, QString("Error inserting into new table %1").arg(t.name())); 2629 return false; 2630 } 2631 query.finish(); 2632 } 2633 if (!query.exec(QString("DROP TABLE " + tempTableName + ';'))) { 2634 buildError(query, Q_FUNC_INFO, QString("Error dropping old table %1").arg(t.name())); 2635 return false; 2636 } 2637 query.finish(); 2638 return true; 2639 } 2640 clean()2641 void clean() 2642 { 2643 Q_Q(MyMoneyStorageSql); 2644 // delete all existing records 2645 QMap<QString, MyMoneyDbTable>::ConstIterator it = m_db.tableBegin(); 2646 QSqlQuery query(*q); 2647 while (it != m_db.tableEnd()) { 2648 query.prepare(QString("DELETE from %1;").arg(it.key())); 2649 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("cleaning database"); // krazy:exclude=crashy 2650 ++it; 2651 } 2652 } 2653 isEmpty()2654 int isEmpty() 2655 { 2656 Q_Q(MyMoneyStorageSql); 2657 // check all tables are empty 2658 QMap<QString, MyMoneyDbTable>::ConstIterator tt = m_db.tableBegin(); 2659 int recordCount = 0; 2660 QSqlQuery query(*q); 2661 while ((tt != m_db.tableEnd()) && (recordCount == 0)) { 2662 query.prepare(QString("select count(*) from %1;").arg((*tt).name())); 2663 if (!query.exec()) throw MYMONEYEXCEPTIONSQL("getting record count"); // krazy:exclude=crashy 2664 if (!query.next()) throw MYMONEYEXCEPTIONSQL("retrieving record count"); 2665 recordCount += query.value(0).toInt(); 2666 ++tt; 2667 } 2668 2669 // a fresh created database contains at least one record (see createTables()) in 2670 // the kmmFileInfo table providing file and fix version. So we report empty 2671 // even if there is a recordCount of 1 2672 if (recordCount > 1) { 2673 return -1; // not empty 2674 } else { 2675 return 0; 2676 } 2677 } 2678 2679 // for bug 252841 tables(QSql::TableType tt)2680 QStringList tables(QSql::TableType tt) 2681 { 2682 Q_Q(MyMoneyStorageSql); 2683 return (m_driver->tables(tt, static_cast<const QSqlDatabase&>(*q))); 2684 } 2685 2686 //! Returns 1 in case the @a column exists in @a table, 0 if not. In case of error, -1 is returned. haveColumnInTable(const QString & table,const QString & column)2687 int haveColumnInTable(const QString& table, const QString& column) 2688 { 2689 Q_Q(MyMoneyStorageSql); 2690 QSqlQuery query(*q); 2691 QString cmd = QString("SELECT * FROM %1 LIMIT 1").arg(table); 2692 if(!query.exec(cmd)) { 2693 buildError(query, Q_FUNC_INFO, QString("Error detecting if %1 exists in %2").arg(column).arg(table)); 2694 return -1; 2695 } 2696 QSqlRecord rec = query.record(); 2697 return (rec.indexOf(column) != -1) ? 1 : 0; 2698 } 2699 2700 /** 2701 * @brief Ensure the storagePlugin with iid was setup 2702 * 2703 * @throws MyMoneyException in case of an error which makes the use 2704 * of the plugin unavailable. 2705 */ setupStoragePlugin(QString iid)2706 bool setupStoragePlugin(QString iid) 2707 { 2708 Q_Q(MyMoneyStorageSql); 2709 // setupDatabase has to be called every time because this simple technique to check if was updated already 2710 // does not work if a user opens another file 2711 // also the setup is removed if the current database transaction is rolled back 2712 if (iid.isEmpty() /*|| m_loadedStoragePlugins.contains(iid)*/) 2713 return false; 2714 2715 MyMoneyDbTransaction t(*q, Q_FUNC_INFO); 2716 auto rc = false; 2717 if (iid == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) 2718 rc = setupIBANBIC(*q); 2719 else if (iid == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) 2720 rc = setupNationalAccount(*q); 2721 else if (iid == sepaOnlineTransferImpl::name()) 2722 rc = setupSepaOnlineTransfer(*q); 2723 else 2724 rc = false; 2725 return rc; 2726 } 2727 setupIBANBIC(QSqlDatabase connection)2728 bool setupIBANBIC(QSqlDatabase connection) 2729 { 2730 auto iid = QLatin1String("org.kmymoney.payeeIdentifier.ibanbic.sqlStoragePlugin"); 2731 // Get current version 2732 QSqlQuery query = QSqlQuery(connection); 2733 query.prepare("SELECT versionMajor FROM kmmPluginInfo WHERE iid = ?"); 2734 query.bindValue(0, iid); 2735 if (!query.exec()) { 2736 qWarning("Could not execute query for ibanBicStoragePlugin: %s", qPrintable(query.lastError().text())); 2737 return false; 2738 } 2739 2740 int currentVersion = 0; 2741 if (query.next()) 2742 currentVersion = query.value(0).toInt(); 2743 2744 // Create database in it's most recent version if version is 0 2745 // (version 0 means the database was not installed) 2746 if (currentVersion == 0) { 2747 // If the database is recreated the table may be still there. So drop it if needed. No error handling needed 2748 // as this step is not necessary - only the creation is important. 2749 if (!query.exec("DROP TABLE IF EXISTS kmmIbanBic;")) 2750 return false; 2751 2752 if (!query.exec( 2753 "CREATE TABLE kmmIbanBic (" 2754 " id varchar(32) NOT NULL PRIMARY KEY REFERENCES kmmPayeeIdentifier( id ) ON DELETE CASCADE ON UPDATE CASCADE," 2755 " iban varchar(32)," 2756 " bic char(11) CHECK(length(bic) = 11 OR bic IS NULL)," 2757 " name text" 2758 " );" 2759 )) { 2760 qWarning("Could not create table for ibanBicStoragePlugin: %s", qPrintable(query.lastError().text())); 2761 return false; 2762 } 2763 2764 query.prepare("INSERT INTO kmmPluginInfo (iid, versionMajor, versionMinor, uninstallQuery) VALUES(?, ?, ?, ?)"); 2765 query.bindValue(0, iid); 2766 query.bindValue(1, 1); 2767 query.bindValue(2, 0); 2768 query.bindValue(3, "DROP TABLE kmmIbanBic;"); 2769 if (query.exec()) 2770 return true; 2771 qWarning("Could not save plugin info for ibanBicStoragePlugin (%s): %s", qPrintable(iid), qPrintable(query.lastError().text())); 2772 return false; 2773 } 2774 2775 // Check if version is valid with this plugin 2776 switch (currentVersion) { 2777 case 1: return true; 2778 } 2779 2780 return false; 2781 } 2782 setupNationalAccount(QSqlDatabase connection)2783 bool setupNationalAccount(QSqlDatabase connection) 2784 { 2785 auto iid = QLatin1String("org.kmymoney.payeeIdentifier.nationalAccount.sqlStoragePlugin"); 2786 // Get current version 2787 QSqlQuery query = QSqlQuery(connection); 2788 query.prepare("SELECT versionMajor FROM kmmPluginInfo WHERE iid = ?"); 2789 query.bindValue(0, iid); 2790 if (!query.exec()) { 2791 qWarning("Could not execute query for nationalAccountStoragePlugin: %s", qPrintable(query.lastError().text())); 2792 return false; 2793 } 2794 2795 int currentVersion = 0; 2796 if (query.next()) 2797 currentVersion = query.value(0).toInt(); 2798 2799 // Create database in it's most recent version if version is 0 2800 // (version 0 means the database was not installed) 2801 if (currentVersion == 0) { 2802 // If the database is recreated the table may be still there. So drop it if needed. No error handling needed 2803 // as this step is not necessary - only the creation is important. 2804 if (!query.exec("DROP TABLE IF EXISTS kmmNationalAccountNumber;")) 2805 return false; 2806 2807 if (!query.exec( 2808 "CREATE TABLE kmmNationalAccountNumber (" 2809 " id varchar(32) NOT NULL PRIMARY KEY REFERENCES kmmPayeeIdentifier( id ) ON DELETE CASCADE ON UPDATE CASCADE," 2810 " countryCode varchar(3)," 2811 " accountNumber TEXT," 2812 " bankCode TEXT," 2813 " name TEXT" 2814 " );" 2815 )) { 2816 qWarning("Could not create table for nationalAccountStoragePlugin: %s", qPrintable(query.lastError().text())); 2817 return false; 2818 } 2819 2820 query.prepare("INSERT INTO kmmPluginInfo (iid, versionMajor, versionMinor, uninstallQuery) VALUES(?, ?, ?, ?)"); 2821 query.bindValue(0, iid); 2822 query.bindValue(1, 1); 2823 query.bindValue(2, 0); 2824 query.bindValue(3, "DROP TABLE kmmNationalAccountNumber;"); 2825 if (query.exec()) 2826 return true; 2827 qWarning("Could not save plugin info for nationalAccountStoragePlugin (%s): %s", qPrintable(iid), qPrintable(query.lastError().text())); 2828 return false; 2829 } 2830 2831 // Check if version is valid with this plugin 2832 switch (currentVersion) { 2833 case 1: return true; 2834 } 2835 2836 return false; 2837 } 2838 setupSepaOnlineTransfer(QSqlDatabase connection)2839 bool setupSepaOnlineTransfer(QSqlDatabase connection) 2840 { 2841 auto iid = QLatin1String("org.kmymoney.creditTransfer.sepa.sqlStoragePlugin"); 2842 // Get current version 2843 QSqlQuery query = QSqlQuery(connection); 2844 query.prepare("SELECT versionMajor FROM kmmPluginInfo WHERE iid = ?"); 2845 query.bindValue(0, iid); 2846 if (!query.exec()) { 2847 qWarning("Could not execute query for sepaStoragePlugin: %s", qPrintable(query.lastError().text())); 2848 return false; 2849 } 2850 2851 int currentVersion = 0; 2852 if (query.next()) 2853 currentVersion = query.value(0).toInt(); 2854 2855 // Create database in it's most recent version if version is 0 2856 // (version 0 means the database was not installed) 2857 if (currentVersion <= 1) { 2858 // If the database is recreated the table may be still there. So drop it if needed. No error handling needed 2859 // as this step is not necessary - only the creation is important. 2860 if (!query.exec("DROP TABLE IF EXISTS kmmSepaOrders;")) 2861 return false; 2862 2863 if (!query.exec( 2864 "CREATE TABLE kmmSepaOrders (" 2865 " id varchar(32) NOT NULL PRIMARY KEY REFERENCES kmmOnlineJobs( id ) ON UPDATE CASCADE ON DELETE CASCADE," 2866 " originAccount varchar(32) REFERENCES kmmAccounts( id ) ON UPDATE CASCADE ON DELETE SET NULL," 2867 " value text," 2868 " purpose text," 2869 " endToEndReference varchar(35)," 2870 " beneficiaryName varchar(27)," 2871 " beneficiaryIban varchar(32)," 2872 " beneficiaryBic char(11)," 2873 " textKey int," 2874 " subTextKey int" 2875 " );" 2876 )) { 2877 qWarning("Error while creating table kmmSepaOrders: %s", qPrintable(query.lastError().text())); 2878 return false; 2879 } 2880 2881 query.prepare("DELETE FROM kmmPluginInfo WHERE iid = ?;"); 2882 query.bindValue(0, iid); 2883 query.exec(); 2884 2885 query.prepare("INSERT INTO kmmPluginInfo (iid, versionMajor, versionMinor, uninstallQuery) VALUES(?, ?, ?, ?)"); 2886 query.bindValue(0, iid); 2887 query.bindValue(1, 2); 2888 query.bindValue(2, 0); 2889 query.bindValue(3, "DROP TABLE kmmSepaOrders;"); 2890 if (query.exec()) 2891 return true; 2892 qWarning("Error while inserting kmmPluginInfo for '%s': %s", qPrintable(iid), qPrintable(query.lastError().text())); 2893 return false; 2894 } 2895 2896 // Check if version is valid with this plugin 2897 switch (currentVersion) { 2898 case 2: return true; 2899 } 2900 2901 return false; 2902 } 2903 actOnIBANBICObjectInSQL(SQLAction action,const payeeIdentifier & obj)2904 bool actOnIBANBICObjectInSQL(SQLAction action, const payeeIdentifier &obj) 2905 { 2906 payeeIdentifierTyped<payeeIdentifiers::ibanBic> payeeIdentifier = payeeIdentifierTyped<payeeIdentifiers::ibanBic>(obj); 2907 2908 Q_Q(MyMoneyStorageSql); 2909 QSqlQuery query(*q); 2910 2911 auto writeQuery = [&]() { 2912 query.bindValue(":id", obj.idString()); 2913 query.bindValue(":iban", payeeIdentifier->electronicIban()); 2914 const auto bic = payeeIdentifier->fullStoredBic(); 2915 query.bindValue(":bic", (bic.isEmpty()) ? QVariant(QVariant::String) : bic); 2916 query.bindValue(":name", payeeIdentifier->ownerName()); 2917 if (!query.exec()) { // krazy:exclude=crashy 2918 qWarning("Error while saving ibanbic data for '%s': %s", qPrintable(obj.idString()), qPrintable(query.lastError().text())); 2919 return false; 2920 } 2921 return true; 2922 }; 2923 2924 switch(action) { 2925 case SQLAction::Save: 2926 query.prepare("INSERT INTO kmmIbanBic " 2927 " ( id, iban, bic, name )" 2928 " VALUES( :id, :iban, :bic, :name ) " 2929 ); 2930 return writeQuery(); 2931 2932 case SQLAction::Modify: 2933 query.prepare("UPDATE kmmIbanBic SET iban = :iban, bic = :bic, name = :name WHERE id = :id;"); 2934 return writeQuery(); 2935 2936 case SQLAction::Remove: 2937 query.prepare("DELETE FROM kmmIbanBic WHERE id = ?;"); 2938 query.bindValue(0, obj.idString()); 2939 if (!query.exec()) { 2940 qWarning("Error while deleting ibanbic data '%s': %s", qPrintable(obj.idString()), qPrintable(query.lastError().text())); 2941 return false; 2942 } 2943 return true; 2944 } 2945 return false; 2946 } 2947 actOnNationalAccountObjectInSQL(SQLAction action,const payeeIdentifier & obj)2948 bool actOnNationalAccountObjectInSQL(SQLAction action, const payeeIdentifier &obj) 2949 { 2950 payeeIdentifierTyped<payeeIdentifiers::nationalAccount> payeeIdentifier = payeeIdentifierTyped<payeeIdentifiers::nationalAccount>(obj); 2951 2952 Q_Q(MyMoneyStorageSql); 2953 QSqlQuery query(*q); 2954 2955 auto writeQuery = [&]() { 2956 query.bindValue(":id", obj.idString()); 2957 query.bindValue(":countryCode", payeeIdentifier->country()); 2958 query.bindValue(":accountNumber", payeeIdentifier->accountNumber()); 2959 query.bindValue(":bankCode", (payeeIdentifier->bankCode().isEmpty()) ? QVariant(QVariant::String) : payeeIdentifier->bankCode()); 2960 query.bindValue(":name", payeeIdentifier->ownerName()); 2961 if (!query.exec()) { // krazy:exclude=crashy 2962 qWarning("Error while saving national account number for '%s': %s", qPrintable(obj.idString()), qPrintable(query.lastError().text())); 2963 return false; 2964 } 2965 return true; 2966 2967 }; 2968 2969 switch(action) { 2970 case SQLAction::Save: 2971 query.prepare("INSERT INTO kmmNationalAccountNumber " 2972 " ( id, countryCode, accountNumber, bankCode, name )" 2973 " VALUES( :id, :countryCode, :accountNumber, :bankCode, :name ) " 2974 ); 2975 return writeQuery(); 2976 2977 case SQLAction::Modify: 2978 query.prepare("UPDATE kmmNationalAccountNumber SET countryCode = :countryCode, accountNumber = :accountNumber, bankCode = :bankCode, name = :name WHERE id = :id;"); 2979 return writeQuery(); 2980 2981 case SQLAction::Remove: 2982 query.prepare("DELETE FROM kmmNationalAccountNumber WHERE id = ?;"); 2983 query.bindValue(0, obj.idString()); 2984 if (!query.exec()) { 2985 qWarning("Error while deleting national account number '%s': %s", qPrintable(obj.idString()), qPrintable(query.lastError().text())); 2986 return false; 2987 } 2988 return true; 2989 } 2990 return false; 2991 } 2992 actOnSepaOnlineTransferObjectInSQL(SQLAction action,const onlineTask & obj,const QString & id)2993 bool actOnSepaOnlineTransferObjectInSQL(SQLAction action, const onlineTask &obj, const QString& id) 2994 { 2995 Q_Q(MyMoneyStorageSql); 2996 QSqlQuery query(*q); 2997 const auto& task = dynamic_cast<const sepaOnlineTransferImpl &>(obj); 2998 2999 auto bindValuesToQuery = [&]() { 3000 auto value = task.value().toString(); 3001 if (value.isEmpty()) { 3002 value = QStringLiteral("0"); 3003 } 3004 query.bindValue(":id", id); 3005 query.bindValue(":originAccount", task.responsibleAccount()); 3006 query.bindValue(":value", value); 3007 query.bindValue(":purpose", task.purpose()); 3008 query.bindValue(":endToEndReference", (task.endToEndReference().isEmpty()) ? QVariant() : QVariant::fromValue(task.endToEndReference())); 3009 query.bindValue(":beneficiaryName", task.beneficiaryTyped().ownerName()); 3010 query.bindValue(":beneficiaryIban", task.beneficiaryTyped().electronicIban()); 3011 query.bindValue(":beneficiaryBic", (task.beneficiaryTyped().storedBic().isEmpty()) ? QVariant() : QVariant::fromValue(task.beneficiaryTyped().storedBic())); 3012 query.bindValue(":textKey", task.textKey()); 3013 query.bindValue(":subTextKey", task.subTextKey()); 3014 }; 3015 3016 switch(action) { 3017 case SQLAction::Save: 3018 query.prepare("INSERT INTO kmmSepaOrders (" 3019 " id, originAccount, value, purpose, endToEndReference, beneficiaryName, beneficiaryIban, " 3020 " beneficiaryBic, textKey, subTextKey) " 3021 " VALUES( :id, :originAccount, :value, :purpose, :endToEndReference, :beneficiaryName, :beneficiaryIban, " 3022 " :beneficiaryBic, :textKey, :subTextKey ) " 3023 ); 3024 bindValuesToQuery(); 3025 if (!query.exec()) { 3026 qWarning("Error while saving sepa order '%s': %s", qPrintable(id), qPrintable(query.lastError().text())); 3027 return false; 3028 } 3029 return true; 3030 3031 case SQLAction::Modify: 3032 query.prepare( 3033 "UPDATE kmmSepaOrders SET" 3034 " originAccount = :originAccount," 3035 " value = :value," 3036 " purpose = :purpose," 3037 " endToEndReference = :endToEndReference," 3038 " beneficiaryName = :beneficiaryName," 3039 " beneficiaryIban = :beneficiaryIban," 3040 " beneficiaryBic = :beneficiaryBic," 3041 " textKey = :textKey," 3042 " subTextKey = :subTextKey " 3043 " WHERE id = :id"); 3044 bindValuesToQuery(); 3045 if (!query.exec()) { 3046 qWarning("Could not modify sepaOnlineTransfer '%s': %s", qPrintable(id), qPrintable(query.lastError().text())); 3047 return false; 3048 } 3049 return true; 3050 3051 case SQLAction::Remove: 3052 query.prepare("DELETE FROM kmmSepaOrders WHERE id = ?"); 3053 query.bindValue(0, id); 3054 return query.exec(); 3055 } 3056 return false; 3057 } 3058 actOnPayeeIdentifierObjectInSQL(SQLAction action,const payeeIdentifier & obj)3059 void actOnPayeeIdentifierObjectInSQL(SQLAction action, const payeeIdentifier& obj) 3060 { 3061 setupStoragePlugin(obj->payeeIdentifierId()); 3062 auto isSuccessfull = false; 3063 3064 if (obj->payeeIdentifierId() == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) 3065 isSuccessfull = actOnIBANBICObjectInSQL(action, obj); 3066 else if (obj->payeeIdentifierId() == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) 3067 isSuccessfull = actOnNationalAccountObjectInSQL(action, obj); 3068 3069 if (!isSuccessfull) { 3070 switch (action) { 3071 case SQLAction::Save: 3072 throw MYMONEYEXCEPTION(QString::fromLatin1("Could not save object with id '%1' in database (plugin failed).").arg(obj.idString())); 3073 case SQLAction::Modify: 3074 throw MYMONEYEXCEPTION(QString::fromLatin1("Could not modify object with id '%1' in database (plugin failed).").arg(obj.idString())); 3075 case SQLAction::Remove: 3076 throw MYMONEYEXCEPTION(QString::fromLatin1("Could not remove object with id '%1' from database (plugin failed).").arg(obj.idString())); 3077 } 3078 } 3079 } 3080 actOnOnlineJobInSQL(SQLAction action,const onlineTask & obj,const QString & id)3081 void actOnOnlineJobInSQL(SQLAction action, const onlineTask& obj, const QString& id) 3082 { 3083 setupStoragePlugin(obj.taskName()); 3084 auto isSuccessfull = false; 3085 3086 if (obj.taskName() == sepaOnlineTransferImpl::name()) 3087 isSuccessfull = actOnSepaOnlineTransferObjectInSQL(action, obj, id); 3088 3089 if (!isSuccessfull) { 3090 switch (action) { 3091 case SQLAction::Save: 3092 throw MYMONEYEXCEPTION(QString::fromLatin1("Could not save object with id '%1' in database (plugin failed).").arg(id)); 3093 case SQLAction::Modify: 3094 throw MYMONEYEXCEPTION(QString::fromLatin1("Could not modify object with id '%1' in database (plugin failed).").arg(id)); 3095 case SQLAction::Remove: 3096 throw MYMONEYEXCEPTION(QString::fromLatin1("Could not remove object with id '%1' from database (plugin failed).").arg(id)); 3097 } 3098 } 3099 } 3100 createIBANBICObject(QSqlDatabase db,const QString & identId)3101 payeeIdentifierData* createIBANBICObject(QSqlDatabase db, const QString& identId) const 3102 { 3103 QSqlQuery query(db); 3104 query.prepare("SELECT iban, bic, name FROM kmmIbanBic WHERE id = ?;"); 3105 query.bindValue(0, identId); 3106 if (!query.exec() || !query.next()) { 3107 qWarning("Could load iban bic identifier from database"); 3108 return nullptr; 3109 } 3110 3111 payeeIdentifiers::ibanBic *const ident = new payeeIdentifiers::ibanBic; 3112 ident->setIban(query.value(0).toString()); 3113 ident->setBic(query.value(1).toString()); 3114 ident->setOwnerName(query.value(2).toString()); 3115 return ident; 3116 } 3117 createNationalAccountObject(QSqlDatabase db,const QString & identId)3118 payeeIdentifierData* createNationalAccountObject(QSqlDatabase db, const QString& identId) const 3119 { 3120 QSqlQuery query(db); 3121 query.prepare("SELECT countryCode, accountNumber, bankCode, name FROM kmmNationalAccountNumber WHERE id = ?;"); 3122 query.bindValue(0, identId); 3123 if (!query.exec() || !query.next()) { 3124 qWarning("Could load national account number from database"); 3125 return nullptr; 3126 } 3127 3128 payeeIdentifiers::nationalAccount *const ident = new payeeIdentifiers::nationalAccount; 3129 ident->setCountry(query.value(0).toString()); 3130 ident->setAccountNumber(query.value(1).toString()); 3131 ident->setBankCode(query.value(2).toString()); 3132 ident->setOwnerName(query.value(3).toString()); 3133 return ident; 3134 } 3135 createPayeeIdentifierObject(QSqlDatabase db,const QString & identifierType,const QString & identifierId)3136 payeeIdentifier createPayeeIdentifierObject(QSqlDatabase db, const QString& identifierType, const QString& identifierId) const 3137 { 3138 payeeIdentifierData* identData = nullptr; 3139 if (identifierType == payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()) 3140 identData = createIBANBICObject(db, identifierId); 3141 else if (identifierType == payeeIdentifiers::nationalAccount::staticPayeeIdentifierIid()) 3142 identData = createNationalAccountObject(db, identifierId); 3143 3144 return payeeIdentifier(identifierId, identData); 3145 } 3146 createSepaOnlineTransferObject(QSqlDatabase connection,const QString & onlineJobId)3147 onlineTask* createSepaOnlineTransferObject(QSqlDatabase connection, const QString& onlineJobId) const 3148 { 3149 Q_ASSERT(!onlineJobId.isEmpty()); 3150 Q_ASSERT(connection.isOpen()); 3151 3152 QSqlQuery query = QSqlQuery( 3153 "SELECT originAccount, value, purpose, endToEndReference, beneficiaryName, beneficiaryIban, " 3154 " beneficiaryBic, textKey, subTextKey FROM kmmSepaOrders WHERE id = ?", 3155 connection 3156 ); 3157 query.bindValue(0, onlineJobId); 3158 if (query.exec() && query.next()) { 3159 sepaOnlineTransferImpl* task = new sepaOnlineTransferImpl(); 3160 task->setOriginAccount(query.value(0).toString()); 3161 task->setValue(MyMoneyMoney(query.value(1).toString())); 3162 task->setPurpose(query.value(2).toString()); 3163 task->setEndToEndReference(query.value(3).toString()); 3164 task->setTextKey(query.value(7).toUInt()); 3165 task->setSubTextKey(query.value(8).toUInt()); 3166 3167 payeeIdentifiers::ibanBic beneficiary; 3168 beneficiary.setOwnerName(query.value(4).toString()); 3169 beneficiary.setIban(query.value(5).toString()); 3170 beneficiary.setBic(query.value(6).toString()); 3171 task->setBeneficiary(beneficiary); 3172 return task; 3173 } 3174 3175 return nullptr; 3176 } 3177 createOnlineTaskObject(const QString & iid,const QString & onlineTaskId,QSqlDatabase connection)3178 onlineTask* createOnlineTaskObject(const QString& iid, const QString& onlineTaskId, QSqlDatabase connection) const 3179 { 3180 onlineTask* taskOnline = nullptr; 3181 if (iid == sepaOnlineTransferImpl::name()) { 3182 // @todo This is probably memory leak but for now it works alike to original code 3183 onlineJobAdministration::instance()->registerOnlineTask(new sepaOnlineTransferImpl); 3184 taskOnline = createSepaOnlineTransferObject(connection, onlineTaskId); 3185 } 3186 if (!taskOnline) 3187 qWarning("In the file is a onlineTask for which I could not find the plugin ('%s')", qPrintable(iid)); 3188 3189 return taskOnline; 3190 } 3191 alert(QString s)3192 void alert(QString s) const // FIXME: remove... 3193 { 3194 qDebug() << s; 3195 } 3196 signalProgress(qint64 current,qint64 total,const QString & msg)3197 void signalProgress(qint64 current, qint64 total, const QString& msg) const 3198 { 3199 if (m_progressCallback != 0) 3200 (*m_progressCallback)(current, total, msg); 3201 } 3202 signalProgress(qint64 current,qint64 total)3203 void signalProgress(qint64 current, qint64 total) const 3204 { 3205 signalProgress(current, total, QString()); 3206 } 3207 3208 template<ulong MyMoneyStorageSqlPrivate::* cache> getNextId(const QString & table,const QString & id,const int prefixLength)3209 ulong getNextId(const QString& table, const QString& id, const int prefixLength) const 3210 { 3211 Q_CHECK_PTR(cache); 3212 if (this->*cache == 0) { 3213 MyMoneyStorageSqlPrivate* nonConstThis = const_cast<MyMoneyStorageSqlPrivate*>(this); 3214 nonConstThis->*cache = 1 + nonConstThis->highestNumberFromIdString(table, id, prefixLength); 3215 } 3216 Q_ASSERT(this->*cache > 0); // everything else is never a valid id 3217 return this->*cache; 3218 } 3219 3220 //void startCommitUnit (const QString& callingFunction); 3221 //void endCommitUnit (const QString& callingFunction); 3222 //void cancelCommitUnit (const QString& callingFunction); 3223 3224 MyMoneyStorageSql *q_ptr; 3225 3226 // data 3227 QExplicitlySharedDataPointer<MyMoneyDbDriver> m_driver; 3228 3229 MyMoneyDbDef m_db; 3230 uint m_dbVersion; 3231 MyMoneyStorageMgr *m_storage; 3232 // input options 3233 bool m_loadAll; // preload all data 3234 bool m_override; // override open if already in use 3235 // error message 3236 QString m_error; 3237 // record counts 3238 ulong m_institutions; 3239 ulong m_accounts; 3240 ulong m_payees; 3241 ulong m_tags; 3242 ulong m_transactions; 3243 ulong m_splits; 3244 ulong m_securities; 3245 ulong m_prices; 3246 ulong m_currencies; 3247 ulong m_schedules; 3248 ulong m_reports; 3249 ulong m_kvps; 3250 ulong m_budgets; 3251 ulong m_onlineJobs; 3252 ulong m_payeeIdentifier; 3253 3254 // Cache for next id to use 3255 // value 0 means data is not available and has to be loaded from the database 3256 ulong m_hiIdInstitutions; 3257 ulong m_hiIdPayees; 3258 ulong m_hiIdTags; 3259 ulong m_hiIdAccounts; 3260 ulong m_hiIdTransactions; 3261 ulong m_hiIdSchedules; 3262 ulong m_hiIdSecurities; 3263 ulong m_hiIdReports; 3264 ulong m_hiIdBudgets; 3265 ulong m_hiIdOnlineJobs; 3266 ulong m_hiIdPayeeIdentifier; 3267 ulong m_hiIdCostCenter; 3268 3269 // encrypt option - usage TBD 3270 QString m_encryptData; 3271 3272 /** 3273 * This variable is used to suppress status messages except during 3274 * initial data load and final write 3275 3276 */ 3277 bool m_displayStatus; 3278 3279 /** The following keeps track of commitment units (known as transactions in SQL 3280 * though it would be confusing to use that term within KMM). It is implemented 3281 * as a stack for debug purposes. Long term, probably a count would suffice 3282 */ 3283 QStack<QString> m_commitUnitStack; 3284 /** 3285 * This member variable is used to preload transactions for preferred accounts 3286 */ 3287 MyMoneyTransactionFilter m_preferred; 3288 /** 3289 * This member variable is used because reading prices from a file uses the 'add...' function rather than a 3290 * 'load...' function which other objects use. Having this variable allows us to avoid needing to check the 3291 * database to see if this really is a new or modified price 3292 */ 3293 bool m_readingPrices; 3294 /** 3295 * This member variable holds a map of transaction counts per account, indexed by 3296 * the account id. It is used 3297 * to avoid having to scan all transactions whenever a count is needed. It should 3298 * probably be moved into the MyMoneyAccount object; maybe we will do that once 3299 * the database code has been properly checked out 3300 */ 3301 QHash<QString, ulong> m_transactionCountMap; 3302 /** 3303 * These member variables hold the user name and date/time of logon 3304 */ 3305 QString m_logonUser; 3306 QDateTime m_logonAt; 3307 QDate m_txPostDate; // FIXME: remove when Tom puts date into split object 3308 3309 bool m_newDatabase; 3310 3311 /** 3312 * This member keeps the current precision to be used fro prices. 3313 * @sa setPrecision() 3314 */ 3315 static int m_precision; 3316 3317 /** 3318 * This member keeps the current start date used for transaction retrieval. 3319 * @sa setStartDate() 3320 */ 3321 static QDate m_startDate; 3322 3323 /** 3324 * 3325 */ 3326 QSet<QString> m_loadedStoragePlugins; 3327 3328 void (*m_progressCallback)(int, int, const QString&); 3329 }; 3330 #endif 3331