You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmymoney/kmymoney2/mymoney/storage/mymoneystoragesql.cpp

4504 lines
166 KiB

/***************************************************************************
mymoneystoragesql.cpp
---------------------
begin : 11 November 2005
copyright : (C) 2005 by Tony Bloomfield
email : tonybloom@users.sourceforge.net
: Fernando Vilas <fvilas@iname.com>
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <algorithm>
#include <numeric>
// ----------------------------------------------------------------------------
// QT Includes
#include <tqstring.h>
#include <tqdatetime.h>
#include <tqvaluelist.h>
#include <tqstringlist.h>
#include <tqiodevice.h>
#include <tqsqldriver.h>
// ----------------------------------------------------------------------------
// KDE Includes
#include <klocale.h>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneystoragesql.h"
#include "imymoneyserialize.h"
#include <kmymoney/kmymoneyglobalsettings.h>
#define TRY try {
#define CATCH } catch (MyMoneyException *e) {
#define PASS } catch (MyMoneyException *e) { throw; }
#define ECATCH }
#define DBG(a) // tqDebug(a)
//#define TRACE(a) tqDebug(a)
#define TRACE(a) ::timetrace(a)
//***************** THE CURRENT VERSION OF THE DATABASE LAYOUT ****************
unsigned int MyMoneyDbDef::m_currentVersion = 6;
// subclass TQSqlQuery for performance tracing
MyMoneySqlQuery::MyMoneySqlQuery (MyMoneyStorageSql* db)
: TQSqlQuery (static_cast<TQSqlDatabase*>(db)) {
m_db = db;
}
bool MyMoneySqlQuery::exec () {
TRACE(TQString("start sql - %1").arg(lastQuery()));
bool rc = TQSqlQuery::exec();
TQString msg("end sql\n%1\n***Query returned %2, row count %3");
TRACE (msg.arg(TQSqlQuery::executedQuery()).arg(rc).arg(numRowsAffected()));
//DBG (TQString("%1\n***Query returned %2, row count %3").arg(TQSqlQuery::executedQuery()).arg(rc).arg(size()));
return (rc);
}
bool MyMoneySqlQuery::prepare ( const TQString & query ) {
if (m_db->isSqlite3()) {
TQString newQuery = query;
return (TQSqlQuery::prepare (newQuery.replace("FOR UPDATE", "")));
}
return (TQSqlQuery::prepare (query));
}
//*****************************************************************************
MyMoneyDbDrivers::MyMoneyDbDrivers () {
m_driverMap["TQDB2"] = TQString("IBM DB2");
m_driverMap["QIBASE"] = TQString("Borland Interbase");
m_driverMap["QMYSQL3"] = TQString("MySQL");
m_driverMap["QOCI8"] = TQString("Oracle Call Interface, version 8 and 9");
m_driverMap["QODBC3"] = TQString("Open Database Connectivity");
m_driverMap["QPSQL7"] = TQString("PostgreSQL v6.x and v7.x");
m_driverMap["QTDS7"] = TQString("Sybase Adaptive Server and Microsoft SQL Server");
m_driverMap["QSQLITE3"] = TQString("SQLite Version 3");
}
databaseTypeE MyMoneyDbDrivers::driverToType (const TQString& driver) const {
if (driver == "TQDB2") return(Db2);
else if (driver == "QIBASE") return(Interbase);
else if (driver == "QMYSQL3") return(Mysql);
else if (driver == "QOCI8") return(Oracle8);
else if (driver == "QODBC3") return(ODBC3);
else if (driver == "QPSQL7") return(Postgresql);
else if (driver == "QTDS7") return(Sybase);
else if (driver == "QSQLITE3") return(Sqlite3);
else throw new MYMONEYEXCEPTION (TQString("Unknown database driver type").arg(driver));
}
bool MyMoneyDbDrivers::isTested (databaseTypeE dbType) const {
switch (dbType) {
case Mysql:
case Sqlite3:
case Postgresql:
return (true);
default:
return(false);
}
return(false);
}
//************************ Constructor/Destructor *****************************
MyMoneyStorageSql::MyMoneyStorageSql (IMyMoneySerialize *storage, const KURL& url)
: TQSqlDatabase (url.queryItem("driver"), TQString("kmmdatabase")) {
DBG("*** Entering MyMoneyStorageSql::MyMoneyStorageSql");
m_dbVersion = 0;
m_progressCallback = 0;
m_displayStatus = false;
m_storage = storage;
m_storagePtr = dynamic_cast<IMyMoneyStorage*>(storage);
m_newDatabase = false;
m_readingPrices = false;
m_loadAll = false;
m_override = false;
m_preferred.setReportAllSplits(false);
}
int MyMoneyStorageSql::open(const KURL& url, int openMode, bool clear) {
DBG("*** Entering MyMoneyStorageSql::open");
try {
int rc = 0;
TQString driverName = url.queryItem("driver");
m_dbType = m_drivers.driverToType(driverName);
//get the input options
TQStringList options = TQStringList::split(',', url.queryItem("options"));
m_loadAll = options.contains("loadAll")/*|| m_mode == 0*/;
m_override = options.contains("override");
// create the database connection
TQString dbName = url.path().right(url.path().length() - 1); // remove separator slash
setDatabaseName(dbName);
setHostName(url.host());
setUserName(url.user());
setPassword(url.pass());
switch (openMode) {
case IO_ReadOnly: // OpenDatabase menu entry (or open last file)
case IO_ReadWrite: // Save menu entry with database open
if (!TQSqlDatabase::open()) {
buildError(MyMoneySqlQuery(), __func__, "opening database");
rc = 1;
} else {
rc = createTables(); // check all tables are present, create if not (we may add tables at some time)
}
break;
case IO_WriteOnly: // SaveAs Database - if exists, must be empty, if not will create
// Try to open the database.
// If that fails, try to create the database, then try to open it again.
m_newDatabase = true;
if (!TQSqlDatabase::open()) {
if (createDatabase(url) != 0) {
rc = 1;
} else {
if (!TQSqlDatabase::open()) {
buildError(MyMoneySqlQuery(), __func__, "opening new database");
rc = 1;
} else {
rc = createTables();
}
}
} else {
rc = createTables();
if (rc == 0) {
if (clear) {
clean();
} else {
rc = isEmpty();
}
}
}
break;
default:
tqFatal("%s", TQString("%1 - unknown open mode %2").arg(__func__).arg(openMode).data());
}
if (rc != 0) return (rc);
// bypass logon check if we are creating a database
if (openMode == IO_WriteOnly) return(0);
// check if the database is locked, if not lock it
readFileInfo();
if (!m_logonUser.isEmpty() && (!m_override)) {
m_error = TQString
(i18n("Database apparently in use\nOpened by %1 on %2 at %3.\nOpen anyway?"))
.arg(m_logonUser)
.arg(m_logonAt.date().toString(Qt::ISODate))
.arg(m_logonAt.time().toString("hh.mm.ss"));
tqDebug("%s", m_error.data());
close(false);
rc = -1;
} else {
m_logonUser = url.user() + "@" + url.host();
m_logonAt = TQDateTime::currentDateTime();
writeFileInfo();
}
return(rc);
} catch (TQString& s) {
tqDebug("%s",s.data());
return (1);
}
}
void MyMoneyStorageSql::close(bool logoff) {
DBG("*** Entering MyMoneyStorageSql::close");
if (TQSqlDatabase::open()) {
if (logoff) {
startCommitUnit(__func__);
m_logonUser = TQString();
writeFileInfo();
endCommitUnit(__func__);
}
TQSqlDatabase::close();
TQSqlDatabase::removeDatabase(this);
}
}
int MyMoneyStorageSql::createDatabase (const KURL& url) {
DBG("*** Entering MyMoneyStorageSql::createDatabase");
if (m_dbType == Sqlite3) return(0); // not needed for sqlite
if (!m_dbType == Mysql) {
m_error =
TQString(i18n("Cannot currently create database for driver %1; please create manually")).arg(driverName());
return (1);
}
// create the database (only works for mysql at present)
TQString dbName = url.path().right(url.path().length() - 1); // remove separator slash
TQSqlDatabase *maindb = TQSqlDatabase::addDatabase(driverName());
maindb->setDatabaseName ("mysql");
maindb->setHostName (url.host());
maindb->setUserName (url.user());
maindb->setPassword (url.pass());
maindb->open();
TQSqlQuery qm(maindb);
TQString qs = TQString("CREATE DATABASE %1;").arg(dbName);
qm.prepare (qs);
if (!qm.exec()) {
buildError (qm, __func__, TQString(i18n("Error in create database %1; do you have create permissions?")).arg(dbName));
return (1);
}
TQSqlDatabase::removeDatabase (maindb);
return (0);
}
int MyMoneyStorageSql::upgradeDb() {
DBG("*** Entering MyMoneyStorageSql::upgradeDb");
//signalProgress(0, 1, TQObject::tr("Upgrading database..."));
MyMoneySqlQuery q(this);
q.prepare ("SELECT version FROM kmmFileInfo;");
if (!q.exec() || !q.next()) {
if (!m_newDatabase) {
buildError (q, __func__, "Error retrieving file info(version)");
return(1);
} else {
m_dbVersion = m_db.currentVersion();
m_storage->setFileFixVersion(m_storage->currentFixVersion());
TQSqlQuery q(this);
q.prepare("UPDATE kmmFileInfo SET version = :version, \
fixLevel = :fixLevel;");
q.bindValue(":version", m_dbVersion);
q.bindValue(":fixLevel", m_storage->currentFixVersion());
if (!q.exec()) {
buildError (q, __func__, "Error updating file info(version)");
return(1);
}
return (0);
}
}
// prior to dbv6, 'version' format was 'dbversion.fixLevel+1'
// as of dbv6, these are separate fields
TQString version = q.value(0).toString();
if (version.contains('.')) {
m_dbVersion = q.value(0).toString().section('.', 0, 0).toUInt();
m_storage->setFileFixVersion(q.value(0).toString().section('.', 1, 1).toUInt() - 1);
} else {
m_dbVersion = version.toUInt();
q.prepare ("SELECT fixLevel FROM kmmFileInfo;");
if (!q.exec() || !q.next()) {
buildError (q, __func__, "Error retrieving file info (fixLevel)");
return(1);
}
m_storage->setFileFixVersion(q.value(0).toUInt());
}
int rc = 0;
while ((m_dbVersion < m_db.currentVersion()) && (rc == 0)) {
switch (m_dbVersion) {
case 0:
if ((rc = upgradeToV1()) != 0) return (1);
++m_dbVersion;
break;
case 1:
if ((rc = upgradeToV2()) != 0) return (1);
++m_dbVersion;
break;
case 2:
if ((rc = upgradeToV3()) != 0) return (1);
++m_dbVersion;
break;
case 3:
if ((rc = upgradeToV4()) != 0) return (1);
++m_dbVersion;
break;
case 4:
if ((rc = upgradeToV5()) != 0) return (1);
++m_dbVersion;
break;
case 5:
if ((rc = upgradeToV6()) != 0) return (1);
++m_dbVersion;
break;
case 6:
break;
default:
tqFatal("Unknown version number in database - %d", m_dbVersion);
}
}
// write updated version to DB
//setVersion(TQString("%1.%2").arg(m_dbVersion).arg(m_minorVersion));
q.prepare (TQString("UPDATE kmmFileInfo SET version = :version;"));
q.bindValue(":version", m_dbVersion);
if (!q.exec()) {
buildError (q, __func__, "Error updating db version");
return (1);
}
//signalProgress(-1,-1);
return (0);
}
// SF bug 2779291
// check whether a column appears in a table already; if not, add it
bool MyMoneyStorageSql::addColumn
(const TQString& table, const TQString& col,
const TQString& after)
{
MyMoneyDbTable t = m_db.m_tables[table];
MyMoneyDbTable::field_iterator ft;
const MyMoneyDbColumn* c;
for (ft = t.begin(); ft != t.end(); ++ft) {
c = (*ft);
if (c->name() == col)
break;
}
if (ft == t.end()) tqFatal("addColumn - get it right");
return (addColumn(t, *c, after));
}
bool MyMoneyStorageSql::addColumn
(const MyMoneyDbTable& t, const MyMoneyDbColumn& c,
const TQString& after){
if ((m_dbType == Sqlite3) && (!after.isEmpty()))
tqFatal("sqlite doesn't support 'AFTER'; use sqliteAlterTable");
if (record(t.name()).contains(c.name()))
return (true);
TQSqlQuery q(this);
TQString afterString = ";";
if (!after.isEmpty())
afterString = TQString("AFTER %1;").arg(after);
q.prepare("ALTER TABLE " + t.name() + " ADD COLUMN " +
c.generateDDL(m_dbType) + afterString);
if (!q.exec()) {
buildError (q, __func__,
TQString("Error adding column %1 to table %2").arg(c.name()).arg(t.name()));
return (false);
}
return (true);
}
// analogous to above
bool MyMoneyStorageSql::dropColumn
(const TQString& table, const TQString& col)
{
return (dropColumn(m_db.m_tables[table], col));
}
bool MyMoneyStorageSql::dropColumn
(const MyMoneyDbTable& t, const TQString& col){
if (m_dbType == Sqlite3)
tqFatal("sqlite doesn't support 'DROP COLUMN'; use sqliteAlterTable");
if (!record(t.name()).contains(col))
return (true);
TQSqlQuery q(this);
q.prepare("ALTER TABLE " + t.name() + " DROP COLUMN "
+ col + ";");
if (!q.exec()) {
buildError (q, __func__,
TQString("Error dropping column %1 from table %2").arg(col).arg(t.name()));
return (false);
}
return (true);
}
int MyMoneyStorageSql::upgradeToV1() {
DBG("*** Entering MyMoneyStorageSql::upgradeToV1");
if ((m_dbType == Sqlite) || (m_dbType == Sqlite3)) tqFatal("SQLite upgrade NYI");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
// change kmmSplits pkey to (transactionId, splitId)
q.prepare ("ALTER TABLE kmmSplits ADD PRIMARY KEY (transactionId, splitId);");
if (!q.exec()) {
buildError (q, __func__, "Error updating kmmSplits pkey");
return (1);
}
// change kmmSplits alter checkNumber varchar(32)
q.prepare (m_db.m_tables["kmmSplits"].modifyColumnString(m_dbType, "checkNumber",
MyMoneyDbColumn("checkNumber", "varchar(32)")));
if (!q.exec()) {
buildError (q, __func__, "Error expanding kmmSplits.checkNumber");
return (1);
}
// change kmmSplits add postDate datetime
if (!addColumn(m_db.m_tables["kmmSplits"],
MyMoneyDbDatetimeColumn("postDate")))
return (1);
// initialize it to same value as transaction (do it the long way round)
q.prepare ("SELECT id, postDate FROM kmmTransactions WHERE txType = 'N';");
if (!q.exec()) {
buildError (q, __func__, "Error priming kmmSplits.postDate");
return (1);
}
TQMap<TQString, TQDateTime> tids;
while (q.next()) tids[q.value(0).toString()] = q.value(1).toDateTime();
TQMap<TQString, TQDateTime>::ConstIterator it;
for (it = tids.begin(); it != tids.end(); ++it) {
q.prepare ("UPDATE kmmSplits SET postDate=:postDate WHERE transactionId = :id;");
q.bindValue(":postDate", it.data().toString(Qt::ISODate));
q.bindValue(":id", it.key());
if (!q.exec()) {
buildError (q, __func__, "priming kmmSplits.postDate");
return(1);
}
}
// add index to kmmKeyValuePairs to (kvpType,kvpId)
TQStringList list;
list << "kvpType" << "kvpId";
q.prepare (MyMoneyDbIndex("kmmKeyValuePairs", "kmmKVPtype_id", list, false).generateDDL(m_dbType) + ";");
if (!q.exec()) {
buildError (q, __func__, "Error adding kmmKeyValuePairs index");
return (1);
}
// add index to kmmSplits to (accountId, txType)
list.clear();
list << "accountId" << "txType";
q.prepare (MyMoneyDbIndex("kmmSplits", "kmmSplitsaccount_type", list, false).generateDDL(m_dbType) + ";");
if (!q.exec()) {
buildError (q, __func__, "Error adding kmmSplits index");
return (1);
}
// change kmmSchedulePaymentHistory pkey to (schedId, payDate)
q.prepare ("ALTER TABLE kmmSchedulePaymentHistory ADD PRIMARY KEY (schedId, payDate);");
if (!q.exec()) {
buildError (q, __func__, "Error updating kmmSchedulePaymentHistory pkey");
return (1);
}
// change kmmPrices pkey to (fromId, toId, priceDate)
q.prepare ("ALTER TABLE kmmPrices ADD PRIMARY KEY (fromId, toId, priceDate);");
if (!q.exec()) {
buildError (q, __func__, "Error updating kmmPrices pkey");
return (1);
}
// change kmmReportConfig pkey to (name)
// There wasn't one previously, so no need to drop it.
q.prepare ("ALTER TABLE kmmReportConfig ADD PRIMARY KEY (name);");
if (!q.exec()) {
buildError (q, __func__, "Error updating kmmReportConfig pkey");
return (1);
}
// change kmmFileInfo add budgets unsigned bigint after kvps
if (!addColumn(m_db.m_tables["kmmFileInfo"],
MyMoneyDbIntColumn("budgets", MyMoneyDbIntColumn::BIG, false)))
return (1);
// change kmmFileInfo add hiBudgetId unsigned bigint after hiReportId
if (!addColumn(m_db.m_tables["kmmFileInfo"],
MyMoneyDbIntColumn("hiBudgetId", MyMoneyDbIntColumn::BIG, false)))
return (1);
// change kmmFileInfo add logonUser
if (!addColumn(m_db.m_tables["kmmFileInfo"],
MyMoneyDbColumn("logonUser", "varchar(255)", false)))
return (1);
// change kmmFileInfo add logonAt datetime
if (!addColumn(m_db.m_tables["kmmFileInfo"],
MyMoneyDbDatetimeColumn("logonAt", false)))
return (1);
// change kmmAccounts add transactionCount unsigned bigint as last field
if (!addColumn(m_db.m_tables["kmmAccounts"],
MyMoneyDbIntColumn("transactionCount", MyMoneyDbIntColumn::BIG, false)))
return (1);
// calculate the transaction counts. the application logic defines an account's tx count
// in such a way as to count multiple splits in a tx which reference the same account as one.
// this is the only way I can think of to do this which will work in sqlite too.
// inefficient, but it only gets done once...
// get a list of all accounts so we'll get a zero value for those without txs
q.prepare ("SELECT id FROM kmmAccounts");
if (!q.exec()) {
buildError (q, __func__, "Error retrieving accounts for transaction counting");
return(1);
}
while (q.next()) {
m_transactionCountMap[q.value(0).toCString()] = 0;
}
q.prepare ("SELECT accountId, transactionId FROM kmmSplits WHERE txType = 'N' ORDER BY 1, 2");
if (!q.exec()) {
buildError (q, __func__, "Error retrieving splits for transaction counting");
return(1);
}
TQString lastAcc, lastTx;
while (q.next()) {
TQString thisAcc = q.value(0).toCString();
TQString thisTx = q.value(1).toCString();
if ((thisAcc != lastAcc) || (thisTx != lastTx)) ++m_transactionCountMap[thisAcc];
lastAcc = thisAcc;
lastTx = thisTx;
}
TQMap<TQString, unsigned long>::ConstIterator itm;
q.prepare("UPDATE kmmAccounts SET transactionCount = :txCount WHERE id = :id;");
for (itm = m_transactionCountMap.begin(); itm != m_transactionCountMap.end(); ++itm) {
q.bindValue (":txCount", TQString::number(itm.data()));
q.bindValue (":id", itm.key());
if (!q.exec()) {
buildError(q, __func__, "Error updating transaction count");
return (1);
}
}
m_transactionCountMap.clear();
// there were considerable problems with record counts in V0, so rebuild them
readFileInfo();
m_institutions = getRecCount("kmmInstitutions");
m_accounts = getRecCount("kmmAccounts");
m_payees = getRecCount("kmmPayees");
m_transactions = getRecCount("kmmTransactions WHERE txType = 'N'");
m_splits = getRecCount("kmmSplits");
m_securities = getRecCount("kmmSecurities");
m_prices = getRecCount("kmmPrices");
m_currencies = getRecCount("kmmCurrencies");
m_schedules = getRecCount("kmmSchedules");
m_reports = getRecCount("kmmReportConfig");
m_kvps = getRecCount("kmmKeyValuePairs");
m_budgets = getRecCount("kmmBudgetConfig");
writeFileInfo();
/* if sqlite {
q.prepare("VACUUM;");
if (!q.exec()) {
buildError (q, __func__, "Error vacuuming database");
return(1);
}
}*/
endCommitUnit(__func__);
return (0);
}
int MyMoneyStorageSql::upgradeToV2() {
DBG("*** Entering MyMoneyStorageSql::upgradeToV2");
//SQLite3 now supports ALTER TABLE...ADD COLUMN, so only die if version < 3
//if (m_dbType == Sqlite3) tqFatal("SQLite upgrade NYI");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
// change kmmSplits add price fields
if (!addColumn(m_db.m_tables["kmmSplits"],
MyMoneyDbTextColumn("price")))
return (1);
if (!addColumn(m_db.m_tables["kmmSplits"],
MyMoneyDbTextColumn("priceFormatted")))
return (1);
endCommitUnit(__func__);
return (0);
}
int MyMoneyStorageSql::upgradeToV3() {
DBG("*** Entering MyMoneyStorageSql::upgradeToV3");
//SQLite3 now supports ALTER TABLE...ADD COLUMN, so only die if version < 3
//if (m_dbType == Sqlite3) tqFatal("SQLite upgrade NYI");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
// The default value is given here to populate the column.
q.prepare ("ALTER TABLE kmmSchedules ADD COLUMN " +
MyMoneyDbIntColumn("occurenceMultiplier",
MyMoneyDbIntColumn::SMALL, false, false, true)
.generateDDL(m_dbType) + " DEFAULT 0;");
if (!q.exec()) {
buildError (q, __func__, "Error adding kmmSchedules.occurenceMultiplier");
return (1);
}
//The default is less than any useful value, so as each schedule is hit, it will update
//itself to the appropriate value.
endCommitUnit(__func__);
return 0;
}
int MyMoneyStorageSql::upgradeToV4() {
DBG("*** Entering MyMoneyStorageSql::upgradeToV4");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
TQStringList list;
list << "transactionId" << "splitId";
q.prepare (MyMoneyDbIndex("kmmSplits", "kmmTx_Split", list, false).generateDDL(m_dbType) + ";");
if (!q.exec()) {
buildError (q, __func__, "Error adding kmmSplits index on (transactionId, splitId)");
return (1);
}
endCommitUnit(__func__);
return 0;
}
int MyMoneyStorageSql::upgradeToV5() {
DBG("*** Entering MyMoneyStorageSql::upgradeToV5");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
if (!addColumn(m_db.m_tables["kmmSplits"],
MyMoneyDbTextColumn("bankId")))
return (1);
if (!addColumn(m_db.m_tables["kmmPayees"],
MyMoneyDbTextColumn("notes", MyMoneyDbTextColumn::LONG)))
return (1);
if (!addColumn(m_db.m_tables["kmmPayees"],
MyMoneyDbColumn("defaultAccountId", "varchar(32)")))
return (1);
if (!addColumn(m_db.m_tables["kmmPayees"],
MyMoneyDbIntColumn("matchData", MyMoneyDbIntColumn::TINY,
false)))
return (1);
if (!addColumn(m_db.m_tables["kmmPayees"],
MyMoneyDbColumn("matchIgnoreCase", "char(1)")))
return (1);
if (!addColumn(m_db.m_tables["kmmPayees"],
MyMoneyDbTextColumn("matchKeys")))
return (1);
const MyMoneyDbTable& t = m_db.m_tables["kmmReportConfig"];
if (m_dbType != Sqlite3) {
q.prepare (t.dropPrimaryKeyString(m_dbType));
if (!q.exec()) {
buildError (q, __func__, "Error dropping Report table keys");
return (1);
}
} else {
if (!sqliteAlterTable(t))
return (1);
}
endCommitUnit(__func__);
return 0;
}
int MyMoneyStorageSql::upgradeToV6() {
DBG("*** Entering MyMoneyStorageSql::upgradeToV6");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
// add separate fix level in file info
if (!addColumn("kmmFileInfo", "fixLevel"))
return (1);
// upgrade Mysql to InnoDB transaction-safe engine
if (m_dbType == Mysql) {
for (TQMapConstIterator<TQString, MyMoneyDbTable> tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) {
q.prepare(TQString("ALTER TABLE %1 ENGINE = InnoDB;").arg(tt.data().name()));
if (!q.exec()) {
buildError (q, __func__, "Error updating to InnoDB");
return (1);
}
}
}
// add unique id to reports table
if (!addColumn(m_db.m_tables["kmmReportConfig"],
MyMoneyDbColumn("id", "varchar(32)")))
return(1);
// read and write reports to get ids inserted
readFileInfo();
TQMap<TQString, MyMoneyReport> reportList =
fetchReports();
// the V5 database allowed lots of duplicate reports with no
// way to distinguish between them. The fetchReports call
// will have effectively removed all duplicates
// so we now delete from the db and re-write them
q.prepare("DELETE FROM kmmReportConfig;");
if (!q.exec()) {
buildError (q, __func__, "Error deleting reports");
return (1);
}
unsigned long long hiReportId = 0;
TQMap<TQString, MyMoneyReport>::const_iterator it_r;
for(it_r = reportList.begin(); it_r != reportList.end(); ++it_r) {
MyMoneyReport r = *it_r;
hiReportId = calcHighId(hiReportId, r.id());
q.prepare (m_db.m_tables["kmmReportConfig"].insertString());
writeReport(*it_r, q);
}
m_hiIdReports = hiReportId;
m_storage->loadReportId(m_hiIdReports);
// sqlite3 doesn't support ADD PRIMARY KEY
if (m_dbType == Sqlite3) {
if (!sqliteAlterTable(m_db.m_tables["kmmReportConfig"])) {
return (1);
}
} else {
q.prepare ("ALTER TABLE kmmReportConfig ADD PRIMARY KEY (id);");
if (!q.exec()) {
buildError (q, __func__, "Error updating kmmReportConfig pkey");
return (1);
}
}
endCommitUnit(__func__);
return 0;
}
/* This function attempts to cater for limitations in the sqlite ALTER TABLE
statement. It should enable us to drop a primary key, and drop columns */
bool MyMoneyStorageSql::sqliteAlterTable(const MyMoneyDbTable& t) {
DBG("*** Entering MyMoneyStorageSql::sqliteAlterTable");
TQString tempTableName = t.name();
tempTableName.replace("kmm", "tmp");
TQSqlQuery q(this);
q.prepare (TQString("ALTER TABLE " + t.name() + " RENAME TO " + tempTableName + ";"));
if (!q.exec()) {
buildError (q, __func__, "Error renaming table");
return false;
}
createTable(t);
q.prepare (TQString("INSERT INTO " + t.name() + " (" + t.columnList() +
") SELECT " + t.columnList() + " FROM " + tempTableName + ";"));
if (!q.exec()) {
buildError (q, __func__, "Error inserting into new table");
return false;
}
q.prepare (TQString("DROP TABLE " + tempTableName + ";"));
if (!q.exec()) {
buildError (q, __func__, "Error dropping old table");
return false;
}
return true;
}
long unsigned MyMoneyStorageSql::getRecCount (const TQString& table) const {
DBG("*** Entering MyMoneyStorageSql::getRecCount");
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
q.prepare(TQString("SELECT COUNT(*) FROM %1;").arg(table));
if ((!q.exec()) || (!q.next())) {
buildError (q, __func__, "error retrieving record count");
tqFatal("Error retrieving record count"); // definitely shouldn't happen
}
return ((unsigned long) q.value(0).toULongLong());
}
int MyMoneyStorageSql::createTables () {
DBG("*** Entering MyMoneyStorageSql::createTables");
// check tables, create if required
// convert everything to lower case, since SQL standard is case insensitive
// table and column names (when not delimited), but some DBMSs disagree.
TQStringList lowerTables = tables(TQSql::AllTables);
for (TQStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) {
(*i) = (*i).lower();
}
for (TQMapConstIterator<TQString, MyMoneyDbTable> tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) {
if (!lowerTables.contains(tt.key().lower())) createTable (tt.data());
}
MyMoneySqlQuery q(this);
for (TQMapConstIterator<TQString, MyMoneyDbView> tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) {
if (!lowerTables.contains(tt.key().lower())) {
q.prepare (tt.data().createString());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString ("creating view %1").arg(tt.key())));
}
}
// get the current db version from kmmFileInfo.
// upgrade if necessary.
return (upgradeDb()); // any errors will be caught by exception handling
}
void MyMoneyStorageSql::createTable (const MyMoneyDbTable& t) {
DBG("*** Entering MyMoneyStorageSql::createTable");
// create the tables
TQStringList ql = TQStringList::split('\n', t.generateCreateSQL(m_dbType));
MyMoneySqlQuery q(this);
for (unsigned int i = 0; i < ql.count(); ++i) {
q.prepare (ql[i]);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString ("creating table/index %1").arg(t.name())));
}
}
int MyMoneyStorageSql::isEmpty () {
DBG("*** Entering MyMoneyStorageSql::isEmpty");
// check all tables are empty
TQMapConstIterator<TQString, MyMoneyDbTable> tt = m_db.tableBegin();
int recordCount = 0;
MyMoneySqlQuery q(this);
while ((tt != m_db.tableEnd()) && (recordCount == 0)) {
q.prepare (TQString("select count(*) from %1;").arg((*tt).name()));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "getting record count"));
if (!q.next()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "retrieving record count"));
recordCount += q.value(0).toInt();
++tt;
}
if (recordCount != 0) {
return (-1); // not empty
} else {
return (0);
}
}
void MyMoneyStorageSql::clean() {
DBG("*** Entering MyMoneyStorageSql::clean");
// delete all existing records
TQMapConstIterator<TQString, MyMoneyDbTable> it = m_db.tableBegin();
MyMoneySqlQuery q(this);
while (it != m_db.tableEnd()) {
q.prepare(TQString("DELETE from %1;").arg(it.key()));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString ("cleaning database")));
++it;
}
}
//////////////////////////////////////////////////////////////////
bool MyMoneyStorageSql::readFile(void) {
DBG("*** Entering MyMoneyStorageSql::readFile");
m_displayStatus = true;
try {
readFileInfo();
readInstitutions();
if (m_loadAll) {
readPayees();
} else {
TQValueList<TQString> user;
user.append(TQString("USER"));
readPayees(user);
}
//TRACE("done payees");
readCurrencies();
//TRACE("done currencies");
readSecurities();
//TRACE("done securities");
readAccounts();
if (m_loadAll) {
readTransactions();
} else {
if (m_preferred.filterSet().singleFilter.accountFilter) readTransactions (m_preferred);
}
//TRACE("done accounts");
readSchedules();
//TRACE("done schedules");
readPrices();
//TRACE("done prices");
readReports();
//TRACE("done reports");
readBudgets();
//TRACE("done budgets");
//FIXME - ?? if (m_mode == 0)
//m_storage->rebuildAccountBalances();
// this seems to be nonsense, but it clears the dirty flag
// as a side-effect.
m_storage->setLastModificationDate(m_storage->lastModificationDate());
// FIXME?? if (m_mode == 0) m_storage = NULL;
// make sure the progress bar is not shown any longer
signalProgress(-1, -1);
m_displayStatus = false;
//MyMoneySqlQuery::traceOn();
return true;
} catch (TQString& s) {
return false;
}
}
// The following is called from 'SaveAsDatabase'
bool MyMoneyStorageSql::writeFile(void) {
DBG("*** Entering MyMoneyStorageSql::writeFile");
// initialize record counts and hi ids
m_institutions = m_accounts = m_payees = m_transactions = m_splits
= m_securities = m_prices = m_currencies = m_schedules = m_reports = m_kvps = m_budgets = 0;
m_hiIdInstitutions = m_hiIdPayees = m_hiIdAccounts = m_hiIdTransactions =
m_hiIdSchedules = m_hiIdSecurities = m_hiIdReports = m_hiIdBudgets = 0;
m_displayStatus = true;
try{
startCommitUnit(__func__);
writeInstitutions ();
writePayees();
writeAccounts();
writeTransactions();
writeSchedules();
writeSecurities();
writePrices();
writeCurrencies();
writeReports();
writeBudgets();
writeFileInfo();
// this seems to be nonsense, but it clears the dirty flag
// as a side-effect.
//m_storage->setLastModificationDate(m_storage->lastModificationDate());
// FIXME?? if (m_mode == 0) m_storage = NULL;
endCommitUnit(__func__);
// make sure the progress bar is not shown any longer
signalProgress(-1, -1);
m_displayStatus = false;
return true;
} catch (TQString& s) {
return false;
}
}
// --------------- SQL Transaction (commit unit) handling -----------------------------------
void MyMoneyStorageSql::startCommitUnit (const TQString& callingFunction) {
DBG("*** Entering MyMoneyStorageSql::startCommitUnit");
if (m_commitUnitStack.isEmpty()) {
if (!transaction()) throw new MYMONEYEXCEPTION(buildError (MyMoneySqlQuery(), __func__, "starting commit unit"));
}
m_commitUnitStack.push(callingFunction);
}
bool MyMoneyStorageSql::endCommitUnit (const TQString& callingFunction) {
DBG("*** Entering MyMoneyStorageSql::endCommitUnit");
// for now, we don't know if there were any changes made to the data so
// we expect the data to have changed. This assumption causes some unnecessary
// repaints of the UI here and there, but for now it's ok. If we can determine
// that the commit() really changes the data, we can return that information
// as value of this method.
bool rc = true;
if (callingFunction != m_commitUnitStack.top())
tqDebug("%s", TQString("%1 - %2 s/be %3").arg(__func__).arg(callingFunction).arg(m_commitUnitStack.top()).data());
m_commitUnitStack.pop();
if (m_commitUnitStack.isEmpty()) {
if (!commit()) throw new MYMONEYEXCEPTION(buildError (MyMoneySqlQuery(), __func__, "ending commit unit"));
}
return rc;
}
void MyMoneyStorageSql::cancelCommitUnit (const TQString& callingFunction) {
DBG("*** Entering MyMoneyStorageSql::cancelCommitUnit");
if (callingFunction != m_commitUnitStack.top())
tqDebug("%s", TQString("%1 - %2 s/be %3").arg(__func__).arg(callingFunction).arg(m_commitUnitStack.top()).data());
if (m_commitUnitStack.isEmpty()) return;
m_commitUnitStack.clear();
if (!rollback()) throw new MYMONEYEXCEPTION(buildError (MyMoneySqlQuery(), __func__, "cancelling commit unit"));
}
/////////////////////////////////////////////////////////////////////
void MyMoneyStorageSql::fillStorage() {
DBG("*** Entering MyMoneyStorageSql::fillStorage");
// if (!m_transactionListRead) // make sure we have loaded everything
readTransactions();
// if (!m_payeeListRead)
readPayees();
}
//------------------------------ Write SQL routines ----------------------------------------
// **** Institutions ****
void MyMoneyStorageSql::writeInstitutions() {
DBG("*** Entering MyMoneyStorageSql::writeInstitutions");
// first, get a list of what's on the database
// anything not in the list needs to be inserted
// anything which is will be updated and removed from the list
// anything left over at the end will need to be deleted
// this is an expensive and inconvenient way to do things; find a better way
// one way would be to build the lists when reading the db
// unfortunately this object does not persist between read and write
// it would also be nice if we could tell which objects had been updated since we read them in
TQValueList<TQString> dbList;
MyMoneySqlQuery q(this);
q.prepare("SELECT id FROM kmmInstitutions;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Institution list"));
while (q.next()) dbList.append(q.value(0).toString());
const TQValueList<MyMoneyInstitution> list = m_storage->institutionList();
TQValueList<MyMoneyInstitution>::ConstIterator it;
MyMoneySqlQuery q2(this);
q.prepare (m_db.m_tables["kmmInstitutions"].updateString());
q2.prepare (m_db.m_tables["kmmInstitutions"].insertString());
signalProgress(0, list.count(), "Writing Institutions...");
for(it = list.begin(); it != list.end(); ++it) {
if (dbList.contains((*it).id())) {
dbList.remove ((*it).id());
writeInstitution(*it, q);
} else {
writeInstitution(*it, q2);
}
signalProgress (++m_institutions, 0);
}
if (!dbList.isEmpty()) {
TQValueList<TQString>::const_iterator it = dbList.begin();
q.prepare("DELETE FROM kmmInstitutions WHERE id = :id");
while (it != dbList.end()) {
q.bindValue(":id", (*it));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Institution"));
deleteKeyValuePairs("OFXSETTINGS", (*it));
++it;
}
}
}
void MyMoneyStorageSql::addInstitution(const MyMoneyInstitution& inst) {
DBG("*** Entering MyMoneyStorageSql::addInstitution");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmInstitutions"].insertString());
writeInstitution(inst ,q);
++m_institutions;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifyInstitution(const MyMoneyInstitution& inst) {
DBG("*** Entering MyMoneyStorageSql::modifyInstitution");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmInstitutions"].updateString());
deleteKeyValuePairs("OFXSETTINGS", inst.id());
writeInstitution(inst ,q);
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removeInstitution(const MyMoneyInstitution& inst) {
DBG("*** Entering MyMoneyStorageSql::removeInstitution");
startCommitUnit(__func__);
deleteKeyValuePairs("OFXSETTINGS", inst.id());
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmInstitutions"].deleteString());
q.bindValue(":id", inst.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Institution")));
--m_institutions;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::writeInstitution(const MyMoneyInstitution& i, MyMoneySqlQuery& q) {
DBG("*** Entering MyMoneyStorageSql::writeInstitution");
q.bindValue(":id", i.id());
q.bindValue(":name", i.name());
q.bindValue(":manager", i.manager());
q.bindValue(":routingCode", i.sortcode());
q.bindValue(":addressStreet", i.street());
q.bindValue(":addressCity", i.city());
q.bindValue(":addressZipcode", i.postcode());
q.bindValue(":telephone", i.telephone());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Institution")));
writeKeyValuePairs("OFXSETTINGS", i.id(), i.pairs());
m_hiIdInstitutions = calcHighId(m_hiIdInstitutions, i.id());
}
// **** Payees ****
void MyMoneyStorageSql::writePayees() {
DBG("*** Entering MyMoneyStorageSql::writePayees");
// first, get a list of what's on the database (see writeInstitutions)
TQValueList<TQString> dbList;
MyMoneySqlQuery q(this);
q.prepare("SELECT id FROM kmmPayees;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Payee list"));
while (q.next()) dbList.append(q.value(0).toString());
TQValueList<MyMoneyPayee> list = m_storage->payeeList();
MyMoneyPayee user(TQString("USER"), m_storage->user());
list.prepend(user);
signalProgress(0, list.count(), "Writing Payees...");
MyMoneySqlQuery q2(this);
q.prepare (m_db.m_tables["kmmPayees"].updateString());
q2.prepare (m_db.m_tables["kmmPayees"].insertString());
TQValueList<MyMoneyPayee>::ConstIterator it;
for(it = list.begin(); it != list.end(); ++it) {
if (dbList.contains((*it).id())) {
dbList.remove ((*it).id());
writePayee(*it, q);
} else {
writePayee(*it, q2);
}
signalProgress(++m_payees, 0);
}
if (!dbList.isEmpty()) {
TQValueList<TQString>::const_iterator it = dbList.begin();
q.prepare(m_db.m_tables["kmmPayees"].deleteString());
while (it != dbList.end()) {
q.bindValue(":id", (*it));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Payee"));
m_payees -= q.numRowsAffected();
++it;
}
}
}
void MyMoneyStorageSql::addPayee(const MyMoneyPayee& payee) {
DBG("*** Entering MyMoneyStorageSql::addPayee");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmPayees"].insertString());
writePayee(payee,q);
++m_payees;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifyPayee(const MyMoneyPayee& payee) {
DBG("*** Entering MyMoneyStorageSql::modifyPayee");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmPayees"].updateString());
writePayee(payee,q);
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifyUserInfo(const MyMoneyPayee& payee) {
DBG("*** Entering MyMoneyStorageSql::modifyUserInfo");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmPayees"].updateString());
writePayee(payee,q, true);
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removePayee(const MyMoneyPayee& payee) {
DBG("*** Entering MyMoneyStorageSql::removePayee");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmPayees"].deleteString());
q.bindValue(":id", payee.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Payee")));
--m_payees;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::writePayee(const MyMoneyPayee& p, MyMoneySqlQuery& q, bool isUserInfo) {
DBG("*** Entering MyMoneyStorageSql::writePayee");
if (isUserInfo) {
q.bindValue(":id", "USER");
} else {
q.bindValue(":id", p.id());
}
q.bindValue(":name", p.name());
q.bindValue(":reference", p.reference());
q.bindValue(":email", p.email());
q.bindValue(":addressStreet", p.address());
q.bindValue(":addressCity", p.city());
q.bindValue(":addressZipcode", p.postcode());
q.bindValue(":addressState", p.state());
q.bindValue(":telephone", p.telephone());
q.bindValue(":notes", p.notes());
q.bindValue(":defaultAccountId", p.defaultAccountId());
bool ignoreCase;
TQString matchKeys;
MyMoneyPayee::payeeMatchType type = p.matchData(ignoreCase, matchKeys);
q.bindValue(":matchData", static_cast<unsigned int>(type));
if (ignoreCase) q.bindValue(":matchIgnoreCase", "Y");
else q.bindValue(":matchIgnoreCase", "N");
q.bindValue(":matchKeys", matchKeys);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString ("writing Payee")));
if (!isUserInfo) m_hiIdPayees = calcHighId(m_hiIdPayees, p.id());
}
// **** Accounts ****
void MyMoneyStorageSql::writeAccounts() {
DBG("*** Entering MyMoneyStorageSql::writeAccounts");
// first, get a list of what's on the database (see writeInstitutions)
TQValueList<TQString> dbList;
MyMoneySqlQuery q(this);
q.prepare("SELECT id FROM kmmAccounts;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Account list"));
while (q.next()) dbList.append(q.value(0).toString());
TQValueList<MyMoneyAccount> list;
m_storage->accountList(list);
TQValueList<MyMoneyAccount>::ConstIterator it;
signalProgress(0, list.count(), "Writing Accounts...");
if (dbList.isEmpty()) { // new table, insert standard accounts
q.prepare (m_db.m_tables["kmmAccounts"].insertString());
} else {
q.prepare (m_db.m_tables["kmmAccounts"].updateString());
}
// Attempt to write the standard accounts. For an empty db, this will fail.
TRY
writeAccount(m_storage->asset(), q); ++m_accounts;
writeAccount(m_storage->liability(), q); ++m_accounts;
writeAccount(m_storage->expense(), q); ++m_accounts;
writeAccount(m_storage->income(), q); ++m_accounts;
writeAccount(m_storage->equity(), q); ++m_accounts;
CATCH
delete e;
// If the above failed, assume that the database is empty and create
// the standard accounts by hand before writing them.
MyMoneyAccount acc_l;
acc_l.setAccountType(MyMoneyAccount::Liability);
acc_l.setName("Liability");
MyMoneyAccount liability(STD_ACC_LIABILITY, acc_l);
MyMoneyAccount acc_a;
acc_a.setAccountType(MyMoneyAccount::Asset);
acc_a.setName("Asset");
MyMoneyAccount asset(STD_ACC_ASSET, acc_a);
MyMoneyAccount acc_e;
acc_e.setAccountType(MyMoneyAccount::Expense);
acc_e.setName("Expense");
MyMoneyAccount expense(STD_ACC_EXPENSE, acc_e);
MyMoneyAccount acc_i;
acc_i.setAccountType(MyMoneyAccount::Income);
acc_i.setName("Income");
MyMoneyAccount income(STD_ACC_INCOME, acc_i);
MyMoneyAccount acc_q;
acc_q.setAccountType(MyMoneyAccount::Equity);
acc_q.setName("Equity");
MyMoneyAccount equity(STD_ACC_ETQUITY, acc_q);
writeAccount(asset, q); ++m_accounts;
writeAccount(expense, q); ++m_accounts;
writeAccount(income, q); ++m_accounts;
writeAccount(liability, q); ++m_accounts;
writeAccount(equity, q); ++m_accounts;
ECATCH
int i = 0;
MyMoneySqlQuery q2(this);
q.prepare (m_db.m_tables["kmmAccounts"].updateString());
q2.prepare (m_db.m_tables["kmmAccounts"].insertString());
// Update the accounts that exist; insert the ones that do not.
for(it = list.begin(); it != list.end(); ++it, ++i) {
m_transactionCountMap[(*it).id()] = m_storagePtr->transactionCount((*it).id());
if (dbList.contains((*it).id())) {
dbList.remove ((*it).id());
writeAccount(*it, q);
} else {
writeAccount(*it, q2);
}
signalProgress(++m_accounts, 0);
}
// Delete the accounts that are in the db but no longer in memory.
if (!dbList.isEmpty()) {
TQValueList<TQString>::const_iterator it = dbList.begin();
q.prepare("DELETE FROM kmmAccounts WHERE id = :id");
while (it != dbList.end()) {
if (!m_storagePtr->isStandardAccount(*it)) {
q.bindValue(":id", (*it));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Account"));
deleteKeyValuePairs("ACCOUNT", (*it));
deleteKeyValuePairs("ONLINEBANKING", (*it));
}
++it;
}
}
}
void MyMoneyStorageSql::addAccount(const MyMoneyAccount& acc) {
DBG("*** Entering MyMoneyStorageSql::addAccount");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmAccounts"].insertString());
writeAccount(acc,q);
++m_accounts;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifyAccount(const MyMoneyAccount& acc) {
DBG("*** Entering MyMoneyStorageSql::modifyAccount");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmAccounts"].updateString());
deleteKeyValuePairs("ACCOUNT", acc.id());
deleteKeyValuePairs("ONLINEBANKING", acc.id());
writeAccount(acc,q);
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removeAccount(const MyMoneyAccount& acc) {
DBG("*** Entering MyMoneyStorageSql::removeAccount");
startCommitUnit(__func__);
deleteKeyValuePairs("ACCOUNT", acc.id());
deleteKeyValuePairs("ONLINEBANKING", acc.id());
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmAccounts"].deleteString());
q.bindValue(":id", acc.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Account")));
--m_accounts;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::writeAccount(const MyMoneyAccount& acc, MyMoneySqlQuery& q) {
DBG("*** Entering MyMoneyStorageSql::writeAccount");
//MyMoneyMoney balance = m_storagePtr->balance(acc.id(), TQDate());
q.bindValue(":id", acc.id());
q.bindValue(":institutionId", acc.institutionId());
q.bindValue(":parentId", acc.parentAccountId());
if (acc.lastReconciliationDate() == TQDate())
q.bindValue(":lastReconciled", acc.lastReconciliationDate());
else
q.bindValue(":lastReconciled", TQString(acc.lastReconciliationDate().toString(Qt::ISODate)));
q.bindValue(":lastModified", acc.lastModified());
if (acc.openingDate() == TQDate())
q.bindValue(":openingDate", acc.openingDate());
else
q.bindValue(":openingDate", TQString(acc.openingDate().toString(Qt::ISODate)));
q.bindValue(":accountNumber", acc.number());
q.bindValue(":accountType", acc.accountType());
q.bindValue(":accountTypeString", MyMoneyAccount::accountTypeToString(acc.accountType()));
if (acc.accountType() == MyMoneyAccount::Stock) {
q.bindValue(":isStockAccount", "Y");
} else {
q.bindValue(":isStockAccount", "N");
}
q.bindValue(":accountName", acc.name());
q.bindValue(":description", acc.description());
q.bindValue(":currencyId", acc.currencyId());
// This section attempts to get the balance from the database, if possible
// That way, the balance fields are kept in sync. If that fails, then
// It is assumed that the account actually knows its correct balance.
//FIXME: Using exceptions for branching always feels like a kludge.
// Look for a better way.
TRY
MyMoneyMoney bal = m_storagePtr->balance(acc.id(), TQDate());
q.bindValue(":balance", bal.toString());
q.bindValue(":balanceFormatted",
bal.formatMoney("", -1, false));
CATCH
delete e;
q.bindValue(":balance", acc.balance().toString());
q.bindValue(":balanceFormatted",
acc.balance().formatMoney("", -1, false));
ECATCH
q.bindValue(":transactionCount", TQ_ULLONG(m_transactionCountMap[acc.id()]));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Account")));
//Add in Key-Value Pairs for accounts.
//MMAccount inherits from KVPContainer AND has a KVPContainer member
//so handle both
writeKeyValuePairs("ACCOUNT", acc.id(), acc.pairs());
writeKeyValuePairs("ONLINEBANKING", acc.id(), acc.onlineBankingSettings().pairs());
m_hiIdAccounts = calcHighId(m_hiIdAccounts, acc.id());
}
// **** Transactions and Splits ****
void MyMoneyStorageSql::writeTransactions() {
DBG("*** Entering MyMoneyStorageSql::writeTransactions");
// first, get a list of what's on the database (see writeInstitutions)
TQValueList<TQString> dbList;
MyMoneySqlQuery q(this);
q.prepare("SELECT id FROM kmmTransactions WHERE txType = 'N';");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Transaction list"));
while (q.next()) dbList.append(q.value(0).toString());
MyMoneyTransactionFilter filter;
filter.setReportAllSplits(false);
TQValueList<MyMoneyTransaction> list;
m_storage->transactionList(list, filter);
signalProgress(0, list.count(), "Writing Transactions...");
TQValueList<MyMoneyTransaction>::ConstIterator it;
int i = 0;
MyMoneySqlQuery q2(this);
q.prepare (m_db.m_tables["kmmTransactions"].updateString());
q2.prepare (m_db.m_tables["kmmTransactions"].insertString());
for(it = list.begin(); it != list.end(); ++it, ++i) {
if (dbList.contains((*it).id())) {
dbList.remove ((*it).id());
writeTransaction((*it).id(), *it, q, "N");
} else {
writeTransaction((*it).id(), *it, q2, "N");
}
signalProgress(++m_transactions, 0);
}
if (!dbList.isEmpty()) {
TQValueList<TQString>::const_iterator it = dbList.begin();
while (it != dbList.end()) {
deleteTransaction(*it);
++it;
}
}
}
void MyMoneyStorageSql::addTransaction (const MyMoneyTransaction& tx) {
DBG("*** Entering MyMoneyStorageSql::addTransaction");
startCommitUnit(__func__);
// add the transaction and splits
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmTransactions"].insertString());
writeTransaction(tx.id(), tx, q, "N");
++m_transactions;
// for each split account, update lastMod date, balance, txCount
TQValueList<MyMoneySplit>::ConstIterator it_s;
for(it_s = tx.splits().begin(); it_s != tx.splits().end(); ++it_s) {
//MyMoneyAccount acc = m_storagePtr->account((*it_s).accountId());
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
++m_transactionCountMap[acc.id()];
modifyAccount(acc);
}
// in the fileinfo record, update lastMod, txCount, next TxId
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifyTransaction (const MyMoneyTransaction& tx) {
DBG("*** Entering MyMoneyStorageSql::modifyTransaction");
startCommitUnit(__func__);
// remove the splits of the old tx from the count table
MyMoneySqlQuery q(this);
q.prepare ("SELECT accountId FROM kmmSplits WHERE transactionId = :txId;");
q.bindValue(":txId", tx.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "retrieving old splits"));
while (q.next()) {
TQString id = q.value(0).toCString();
--m_transactionCountMap[id];
}
// add the transaction and splits
q.prepare (m_db.m_tables["kmmTransactions"].updateString());
writeTransaction(tx.id(), tx, q, "N");
// for each split account, update lastMod date, balance, txCount
TQValueList<MyMoneySplit>::ConstIterator it_s;
for(it_s = tx.splits().begin(); it_s != tx.splits().end(); ++it_s) {
//MyMoneyAccount acc = m_storagePtr->account((*it_s).accountId());
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
++m_transactionCountMap[acc.id()];
modifyAccount(acc);
}
writeSplits(tx.id(), "N", tx.splits());
// in the fileinfo record, update lastMod
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removeTransaction(const MyMoneyTransaction& tx) {
DBG("*** Entering MyMoneyStorageSql::removeTransaction");
startCommitUnit(__func__);
deleteTransaction(tx.id());
--m_transactions;
// for each split account, update lastMod date, balance, txCount
TQValueList<MyMoneySplit>::ConstIterator it_s;
for(it_s = tx.splits().begin(); it_s != tx.splits().end(); ++it_s) {
MyMoneyAccount acc = m_storagePtr->account((*it_s).accountId());
--m_transactionCountMap[acc.id()];
modifyAccount(acc);
}
// in the fileinfo record, update lastModDate, txCount
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::deleteTransaction(const TQString& id) {
DBG("*** Entering MyMoneyStorageSql::deleteTransaction");
MyMoneySqlQuery q(this);
q.prepare("DELETE FROM kmmSplits WHERE transactionId = :transactionId;");
q.bindValue(":transactionId", id);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Splits"));
q.prepare ("DELETE FROM kmmKeyValuePairs WHERE kvpType = 'SPLIT' "
"AND kvpId LIKE '" + id + "%'");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Splits KVP"));
m_splits -= q.numRowsAffected();
deleteKeyValuePairs("TRANSACTION", id);
q.prepare(m_db.m_tables["kmmTransactions"].deleteString());
q.bindValue(":id", id);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Transaction"));
}
void MyMoneyStorageSql::writeTransaction(const TQString& txId, const MyMoneyTransaction& tx, MyMoneySqlQuery& q, const TQString& type) {
DBG("*** Entering MyMoneyStorageSql::writeTransaction");
q.bindValue(":id", txId);
q.bindValue(":txType", type);
q.bindValue(":postDate", TQString(tx.postDate().toString(Qt::ISODate)));
q.bindValue(":memo", tx.memo());
q.bindValue(":entryDate", TQString(tx.entryDate().toString(Qt::ISODate)));
q.bindValue(":currencyId", tx.commodity());
q.bindValue(":bankId", tx.bankID());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Transaction")));
m_txPostDate = tx.postDate(); // FIXME: TEMP till Tom puts date in split object
TQValueList<MyMoneySplit> splitList = tx.splits();
writeSplits(txId, type, splitList);
//Add in Key-Value Pairs for transactions.
deleteKeyValuePairs("TRANSACTION", txId);
writeKeyValuePairs("TRANSACTION", txId, tx.pairs());
m_hiIdTransactions = calcHighId(m_hiIdTransactions, tx.id());
}
void MyMoneyStorageSql::writeSplits(const TQString& txId, const TQString& type, const TQValueList<MyMoneySplit>& splitList) {
DBG("*** Entering MyMoneyStorageSql::writeSplits");
// first, get a list of what's on the database (see writeInstitutions)
TQValueList<unsigned int> dbList;
MyMoneySqlQuery q(this);
q.prepare("SELECT splitId FROM kmmSplits where transactionId = :id;");
q.bindValue(":id", txId);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Split list"));
while (q.next()) dbList.append(q.value(0).toUInt());
TQValueList<MyMoneySplit>::const_iterator it;
unsigned int i;
MyMoneySqlQuery q2(this);
q.prepare (m_db.m_tables["kmmSplits"].updateString());
q2.prepare (m_db.m_tables["kmmSplits"].insertString());
for(it = splitList.begin(), i = 0; it != splitList.end(); ++it, ++i) {
if (dbList.contains(i)) {
dbList.remove (i);
writeSplit(txId, (*it), type, i, q);
} else {
++m_splits;
writeSplit(txId, (*it), type, i, q2);
}
}
if (!dbList.isEmpty()) {
q.prepare("DELETE FROM kmmSplits WHERE transactionId = :txId AND splitId = :splitId");
TQValueList<unsigned int>::const_iterator it = dbList.begin();
while (it != dbList.end()) {
q.bindValue(":txId", txId);
q.bindValue(":splitId", *it);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Splits"));
++it;
}
}
}
void MyMoneyStorageSql::writeSplit(const TQString& txId, const MyMoneySplit& split,
const TQString& type, const int splitId, MyMoneySqlQuery& q) {
DBG("*** Entering MyMoneyStorageSql::writeSplit");
q.bindValue(":transactionId", txId);
q.bindValue(":txType", type);
q.bindValue(":splitId", splitId);
q.bindValue(":payeeId", split.payeeId());
if (split.reconcileDate() == TQDate())
q.bindValue(":reconcileDate", split.reconcileDate());
else
q.bindValue(":reconcileDate", TQString(split.reconcileDate().toString(Qt::ISODate)));
q.bindValue(":action", split.action());
q.bindValue(":reconcileFlag", split.reconcileFlag());
q.bindValue(":value", split.value().toString());
q.bindValue(":valueFormatted", split.value()
.formatMoney("", -1, false)
.replace(TQChar(','), TQChar('.')));
q.bindValue(":shares", split.shares().toString());
MyMoneyAccount acc = m_storagePtr->account(split.accountId());
MyMoneySecurity sec = m_storagePtr->security(acc.currencyId());
q.bindValue(":sharesFormatted",
split.shares().
formatMoney("", MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()), false).
replace(TQChar(','), TQChar('.')));
MyMoneyMoney price = split.actualPrice();
if (!price.isZero()) {
q.bindValue(":price", price.toString());
q.bindValue(":priceFormatted", price.formatMoney
("", KMyMoneySettings::pricePrecision(), false)
.replace(TQChar(','), TQChar('.')));
} else {
q.bindValue(":price", TQString());
q.bindValue(":priceFormatted", TQString());
}
q.bindValue(":memo", split.memo());
q.bindValue(":accountId", split.accountId());
q.bindValue(":checkNumber", split.number());
q.bindValue(":postDate", m_txPostDate.toString(Qt::ISODate)); // FIXME: when Tom puts date into split object
q.bindValue(":bankId", split.bankID());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Split")));
deleteKeyValuePairs("SPLIT", txId + TQString::number(splitId));
writeKeyValuePairs("SPLIT", txId + TQString::number(splitId), split.pairs());
}
// **** Schedules ****
void MyMoneyStorageSql::writeSchedules() {
DBG("*** Entering MyMoneyStorageSql::writeSchedules");
// first, get a list of what's on the database (see writeInstitutions)
TQValueList<TQString> dbList;
MyMoneySqlQuery q(this);
q.prepare("SELECT id FROM kmmSchedules;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Schedule list"));
while (q.next()) dbList.append(q.value(0).toString());
const TQValueList<MyMoneySchedule> list = m_storage->scheduleList();
TQValueList<MyMoneySchedule>::ConstIterator it;
MyMoneySqlQuery q2(this);
//TODO: find a way to prepare the queries outside of the loop. writeSchedule()
// modifies the query passed to it, so they have to be re-prepared every pass.
signalProgress(0, list.count(), "Writing Schedules...");
for(it = list.begin(); it != list.end(); ++it) {
q.prepare (m_db.m_tables["kmmSchedules"].updateString());
q2.prepare (m_db.m_tables["kmmSchedules"].insertString());
bool insert = true;
if (dbList.contains((*it).id())) {
dbList.remove ((*it).id());
insert = false;
writeSchedule(*it, q, insert);
} else {
writeSchedule(*it, q2, insert);
}
signalProgress(++m_schedules, 0);
}
if (!dbList.isEmpty()) {
TQValueList<TQString>::const_iterator it = dbList.begin();
while (it != dbList.end()) {
deleteSchedule(*it);
++it;
}
}
}
void MyMoneyStorageSql::addSchedule(const MyMoneySchedule& sched) {
DBG("*** Entering MyMoneyStorageSql::addSchedule");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmSchedules"].insertString());
writeSchedule(sched,q, true);
++m_schedules;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifySchedule(const MyMoneySchedule& sched) {
DBG("*** Entering MyMoneyStorageSql::modifySchedule");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmSchedules"].updateString());
writeSchedule(sched,q, false);
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removeSchedule(const MyMoneySchedule& sched) {
DBG("*** Entering MyMoneyStorageSql::removeSchedule");
startCommitUnit(__func__);
deleteSchedule(sched.id());
--m_schedules;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::deleteSchedule (const TQString& id) {
DBG("*** Entering MyMoneyStorageSql::deleteSchedule");
deleteTransaction(id);
MyMoneySqlQuery q(this);
q.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id");
q.bindValue(":id", id);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Schedule Payment History"));
q.prepare(m_db.m_tables["kmmSchedules"].deleteString());
q.bindValue(":id", id);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Schedule"));
//FIXME: enable when schedules have KVPs.
//deleteKeyValuePairs("SCHEDULE", id);
}
void MyMoneyStorageSql::writeSchedule(const MyMoneySchedule& sch, MyMoneySqlQuery& q, bool insert) {
DBG("*** Entering MyMoneyStorageSql::writeSchedule");
q.bindValue(":id", sch.id());
q.bindValue(":name", sch.name());
q.bindValue(":type", sch.type());
q.bindValue(":typeString", MyMoneySchedule::scheduleTypeToString(sch.type()));
q.bindValue(":occurence", sch.occurencePeriod());
q.bindValue(":occurenceMultiplier", sch.occurenceMultiplier());
q.bindValue(":occurenceString", sch.occurenceToString());
q.bindValue(":paymentType", sch.paymentType());
q.bindValue(":paymentTypeString", MyMoneySchedule::paymentMethodToString(sch.paymentType()));
q.bindValue(":startDate", TQString(sch.startDate().toString(Qt::ISODate)));
q.bindValue(":endDate", TQString(sch.endDate().toString(Qt::ISODate)));
if (sch.isFixed()) {
q.bindValue(":fixed", "Y");
} else {
q.bindValue(":fixed", "N");
}
if (sch.autoEnter()) {
q.bindValue(":autoEnter", "Y");
} else {
q.bindValue(":autoEnter", "N");
}
q.bindValue(":lastPayment", sch.lastPayment());
q.bindValue(":nextPaymentDue", TQString(sch.nextDueDate().toString(Qt::ISODate)));
q.bindValue(":weekendOption", sch.weekendOption());
q.bindValue(":weekendOptionString", MyMoneySchedule::weekendOptionToString(sch.weekendOption()));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Schedules")));
//store the payment history for this scheduled task.
//easiest way is to delete all and re-insert; it's not a high use table
q.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id;");
q.bindValue(":id", sch.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Schedule Payment History")));
q.prepare (m_db.m_tables["kmmSchedulePaymentHistory"].insertString());
TQValueList<TQDate> payments = sch.recordedPayments();
TQValueList<TQDate>::ConstIterator it;
for (it=payments.begin(); it!=payments.end(); ++it) {
q.bindValue(":schedId", sch.id());
q.bindValue(":payDate", TQString((*it).toString(Qt::ISODate)));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Schedule Payment History")));
}
//store the transaction data for this task.
if (!insert) {
q.prepare (m_db.m_tables["kmmTransactions"].updateString());
} else {
q.prepare (m_db.m_tables["kmmTransactions"].insertString());
}
writeTransaction(sch.id(), sch.transaction(), q, "S");
//FIXME: enable when schedules have KVPs.
//Add in Key-Value Pairs for transactions.
//deleteKeyValuePairs("SCHEDULE", sch.id());
//writeKeyValuePairs("SCHEDULE", sch.id(), sch.pairs());
m_hiIdSchedules = calcHighId(m_hiIdSchedules, sch.id());
}
// **** Securities ****
void MyMoneyStorageSql::writeSecurities() {
DBG("*** Entering MyMoneyStorageSql::writeSecurities");
// first, get a list of what's on the database (see writeInstitutions)
TQValueList<TQString> dbList;
MyMoneySqlQuery q(this);
MyMoneySqlQuery q2(this);
q.prepare("SELECT id FROM kmmSecurities;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building security list"));
while (q.next()) dbList.append(q.value(0).toString());
const TQValueList<MyMoneySecurity> securityList = m_storage->securityList();
signalProgress(0, securityList.count(), "Writing Securities...");
q.prepare (m_db.m_tables["kmmSecurities"].updateString());
q2.prepare (m_db.m_tables["kmmSecurities"].insertString());
for(TQValueList<MyMoneySecurity>::ConstIterator it = securityList.begin(); it != securityList.end(); ++it) {
if (dbList.contains((*it).id())) {
dbList.remove ((*it).id());
writeSecurity((*it), q);
} else {
writeSecurity((*it), q2);
}
signalProgress(++m_securities, 0);
}
if (!dbList.isEmpty()) {
q.prepare("DELETE FROM kmmSecurities WHERE id = :id");
q2.prepare("DELETE FROM kmmPrices WHERE fromId = :id OR toId = :id");
TQValueList<TQString>::const_iterator it = dbList.begin();
while (it != dbList.end()) {
q.bindValue(":id", (*it));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Security"));
q2.bindValue(":fromId", (*it));
q2.bindValue(":toId", (*it));
if (!q2.exec()) throw new MYMONEYEXCEPTION(buildError (q2, __func__, "deleting Security"));
deleteKeyValuePairs("SECURITY", (*it));
++it;
}
}
}
void MyMoneyStorageSql::addSecurity(const MyMoneySecurity& sec) {
DBG("*** Entering MyMoneyStorageSql::addSecurity");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmSecurities"].insertString());
writeSecurity(sec,q);
++m_securities;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifySecurity(const MyMoneySecurity& sec) {
DBG("*** Entering MyMoneyStorageSql::modifySecurity");
startCommitUnit(__func__);
deleteKeyValuePairs("SECURITY", sec.id());
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmSecurities"].updateString());
writeSecurity(sec,q);
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removeSecurity(const MyMoneySecurity& sec) {
DBG("*** Entering MyMoneyStorageSql::removeSecurity");
startCommitUnit(__func__);
deleteKeyValuePairs("SECURITY", sec.id());
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmSecurities"].deleteString());
q.bindValue(":id", sec.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Security")));
--m_securities;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::writeSecurity(const MyMoneySecurity& security, MyMoneySqlQuery& q) {
DBG("*** Entering MyMoneyStorageSql::writeSecurity");
q.bindValue(":id", security.id());
q.bindValue(":name", security.name());
q.bindValue(":symbol", security.tradingSymbol());
q.bindValue(":type", static_cast<int>(security.securityType()));
q.bindValue(":typeString", MyMoneySecurity::securityTypeToString(security.securityType()));
q.bindValue(":smallestAccountFraction", security.smallestAccountFraction());
q.bindValue(":tradingCurrency", security.tradingCurrency());
q.bindValue(":tradingMarket", security.tradingMarket());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString ("writing Securities")));
//Add in Key-Value Pairs for security
writeKeyValuePairs("SECURITY", security.id(), security.pairs());
m_hiIdSecurities = calcHighId(m_hiIdSecurities, security.id());
}
// **** Prices ****
void MyMoneyStorageSql::writePrices() {
DBG("*** Entering MyMoneyStorageSql::writePrices");
// due to difficulties in matching and determining deletes
// easiest way is to delete all and re-insert
MyMoneySqlQuery q(this);
q.prepare("DELETE FROM kmmPrices");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Prices")));
m_prices = 0;
const MyMoneyPriceList list = m_storage->priceList();
signalProgress(0, list.count(), "Writing Prices...");
MyMoneyPriceList::ConstIterator it;
for(it = list.begin(); it != list.end(); ++it) {
writePricePair(*it);
}
}
void MyMoneyStorageSql::writePricePair(const MyMoneyPriceEntries& p) {
DBG("*** Entering MyMoneyStorageSql::writePricePair");
MyMoneyPriceEntries::ConstIterator it;
for(it = p.begin(); it != p.end(); ++it) {
writePrice (*it);
signalProgress(++m_prices, 0);
}
}
void MyMoneyStorageSql::addPrice(const MyMoneyPrice& p) {
DBG("*** Entering MyMoneyStorageSql::addPrice");
if (m_readingPrices) return;
// the app always calls addPrice, whether or not there is already one there
startCommitUnit(__func__);
bool newRecord = false;
MyMoneySqlQuery q(this);
TQString s = m_db.m_tables["kmmPrices"].selectAllString(false);
s += " WHERE fromId = :fromId AND toId = :toId AND priceDate = :priceDate;";
q.prepare (s);
q.bindValue(":fromId", p.from());
q.bindValue(":toId", p.to());
q.bindValue(":priceDate", TQString(p.date().toString(Qt::ISODate)));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("finding Price")));
if (q.next()) {
q.prepare(m_db.m_tables["kmmPrices"].updateString());
} else {
q.prepare(m_db.m_tables["kmmPrices"].insertString());
++m_prices;
newRecord = true;
}
q.bindValue(":fromId", p.from());
q.bindValue(":toId", p.to());
q.bindValue(":priceDate", TQString(p.date().toString(Qt::ISODate)));
q.bindValue(":price", p.rate(TQString()).toString());
q.bindValue(":priceFormatted",
p.rate(TQString()).formatMoney("", KMyMoneySettings::pricePrecision()));
q.bindValue(":priceSource", p.source());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Price")));
if (newRecord) writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removePrice(const MyMoneyPrice& p) {
DBG("*** Entering MyMoneyStorageSql::removePrice");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmPrices"].deleteString());
q.bindValue(":fromId", p.from());
q.bindValue(":toId", p.to());
q.bindValue(":priceDate", TQString(p.date().toString(Qt::ISODate)));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Price")));
--m_prices;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::writePrice(const MyMoneyPrice& p) {
DBG("*** Entering MyMoneyStorageSql::writePrice");
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmPrices"].insertString());
q.bindValue(":fromId", p.from());
q.bindValue(":toId", p.to());
q.bindValue(":priceDate", TQString(p.date().toString(Qt::ISODate)));
q.bindValue(":price", p.rate(TQString()).toString());
q.bindValue(":priceFormatted", p.rate(TQString()).formatMoney("", 2));
q.bindValue(":priceSource", p.source());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Prices")));
}
// **** Currencies ****
void MyMoneyStorageSql::writeCurrencies() {
DBG("*** Entering MyMoneyStorageSql::writeCurrencies");
// first, get a list of what's on the database (see writeInstitutions)
TQValueList<TQString> dbList;
MyMoneySqlQuery q(this);
MyMoneySqlQuery q2(this);
q.prepare("SELECT ISOCode FROM kmmCurrencies;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Currency list"));
while (q.next()) dbList.append(q.value(0).toString());
const TQValueList<MyMoneySecurity> currencyList = m_storage->currencyList();
signalProgress(0, currencyList.count(), "Writing Currencies...");
q.prepare (m_db.m_tables["kmmCurrencies"].updateString());
q2.prepare (m_db.m_tables["kmmCurrencies"].insertString());
for(TQValueList<MyMoneySecurity>::ConstIterator it = currencyList.begin(); it != currencyList.end(); ++it) {
if (dbList.contains((*it).id())) {
dbList.remove ((*it).id());
writeCurrency((*it), q);
} else {
writeCurrency((*it), q2);
}
signalProgress(++m_currencies, 0);
}
if (!dbList.isEmpty()) {
q.prepare("DELETE FROM kmmCurrencies WHERE ISOCode = :ISOCode");
TQValueList<TQString>::const_iterator it = dbList.begin();
while (it != dbList.end()) {
q.bindValue(":ISOCode", (*it));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Currency"));
++it;
}
}
}
void MyMoneyStorageSql::addCurrency(const MyMoneySecurity& sec) {
DBG("*** Entering MyMoneyStorageSql::addCurrency");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmCurrencies"].insertString());
writeCurrency(sec,q);
++m_currencies;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifyCurrency(const MyMoneySecurity& sec) {
DBG("*** Entering MyMoneyStorageSql::modifyCurrency");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmCurrencies"].updateString());
writeCurrency(sec,q);
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removeCurrency(const MyMoneySecurity& sec) {
DBG("*** Entering MyMoneyStorageSql::removeCurrency");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmCurrencies"].deleteString());
q.bindValue(":ISOcode", sec.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Currency")));
--m_currencies;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::writeCurrency(const MyMoneySecurity& currency, MyMoneySqlQuery& q) {
DBG("*** Entering MyMoneyStorageSql::writeCurrency");
q.bindValue(":ISOcode", currency.id());
q.bindValue(":name", currency.name());
q.bindValue(":type", static_cast<int>(currency.securityType()));
q.bindValue(":typeString", MyMoneySecurity::securityTypeToString(currency.securityType()));
// writing the symbol as three short ints is a PITA, but the
// problem is that database drivers have incompatible ways of declaring UTF8
TQString symbol = currency.tradingSymbol() + " ";
q.bindValue(":symbol1", symbol.mid(0,1).unicode()->unicode());
q.bindValue(":symbol2", symbol.mid(1,1).unicode()->unicode());
q.bindValue(":symbol3", symbol.mid(2,1).unicode()->unicode());
q.bindValue(":symbolString", symbol);
q.bindValue(":partsPerUnit", currency.partsPerUnit());
q.bindValue(":smallestCashFraction", currency.smallestCashFraction());
q.bindValue(":smallestAccountFraction", currency.smallestAccountFraction());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Currencies")));
}
void MyMoneyStorageSql::writeReports() {
DBG("*** Entering MyMoneyStorageSql::writeReports");
// first, get a list of what's on the database (see writeInstitutions)
TQValueList<TQString> dbList;
MyMoneySqlQuery q(this);
MyMoneySqlQuery q2(this);
q.prepare("SELECT id FROM kmmReportConfig;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Report list"));
while (q.next()) dbList.append(q.value(0).toString());
TQValueList<MyMoneyReport> list = m_storage->reportList();
signalProgress(0, list.count(), "Writing Reports...");
TQValueList<MyMoneyReport>::ConstIterator it;
q.prepare (m_db.m_tables["kmmReportConfig"].updateString());
q2.prepare (m_db.m_tables["kmmReportConfig"].insertString());
for(it = list.begin(); it != list.end(); ++it){
if (dbList.contains((*it).id())) {
dbList.remove ((*it).id());
writeReport(*it, q);
} else {
writeReport(*it, q2);
}
signalProgress(++m_reports, 0);
}
if (!dbList.isEmpty()) {
q.prepare("DELETE FROM kmmReportConfig WHERE id = :id");
TQValueList<TQString>::const_iterator it = dbList.begin();
while (it != dbList.end()) {
q.bindValue(":id", (*it));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Report"));
++it;
}
}
}
void MyMoneyStorageSql::addReport(const MyMoneyReport& rep) {
DBG("*** Entering MyMoneyStorageSql::addReport");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmReportConfig"].insertString());
writeReport(rep,q);
++m_reports;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifyReport(const MyMoneyReport& rep) {
DBG("*** Entering MyMoneyStorageSql::modifyReport");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmReportConfig"].updateString());
writeReport(rep,q);
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removeReport(const MyMoneyReport& rep) {
DBG("*** Entering MyMoneyStorageSql::removeReport");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare("DELETE FROM kmmReportConfig WHERE id = :id");
q.bindValue(":id", rep.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Report")));
--m_reports;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::writeReport (const MyMoneyReport& rep, MyMoneySqlQuery& q) {
DBG("*** Entering MyMoneyStorageSql::writeReport");
TQDomDocument d; // create a dummy XML document
TQDomElement e = d.createElement("REPORTS");
d.appendChild (e);
rep.writeXML(d, e); // write the XML to document
q.bindValue(":id", rep.id());
q.bindValue(":name", rep.name());
q.bindValue(":XML", d.toString());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Reports")));
//m_hiIdReports = calcHighId(m_hiIdReports, rep.id());
}
void MyMoneyStorageSql::writeBudgets() {
DBG("*** Entering MyMoneyStorageSql::writeBudgets");
// first, get a list of what's on the database (see writeInstitutions)
TQValueList<TQString> dbList;
MyMoneySqlQuery q(this);
MyMoneySqlQuery q2(this);
q.prepare("SELECT name FROM kmmBudgetConfig;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "building Budget list"));
while (q.next()) dbList.append(q.value(0).toString());
TQValueList<MyMoneyBudget> list = m_storage->budgetList();
signalProgress(0, list.count(), "Writing Budgets...");
TQValueList<MyMoneyBudget>::ConstIterator it;
q.prepare (m_db.m_tables["kmmBudgetConfig"].updateString());
q2.prepare (m_db.m_tables["kmmBudgetConfig"].insertString());
for(it = list.begin(); it != list.end(); ++it){
if (dbList.contains((*it).name())) {
dbList.remove ((*it).name());
writeBudget(*it, q);
} else {
writeBudget(*it, q2);
}
signalProgress(++m_budgets, 0);
}
if (!dbList.isEmpty()) {
q.prepare("DELETE FROM kmmBudgetConfig WHERE id = :id");
TQValueList<TQString>::const_iterator it = dbList.begin();
while (it != dbList.end()) {
q.bindValue(":name", (*it));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "deleting Budget"));
++it;
}
}
}
void MyMoneyStorageSql::addBudget(const MyMoneyBudget& bud) {
DBG("*** Entering MyMoneyStorageSql::addBudget");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmBudgetConfig"].insertString());
writeBudget(bud,q);
++m_budgets;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::modifyBudget(const MyMoneyBudget& bud) {
DBG("*** Entering MyMoneyStorageSql::modifyBudget");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmBudgetConfig"].updateString());
writeBudget(bud,q);
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::removeBudget(const MyMoneyBudget& bud) {
DBG("*** Entering MyMoneyStorageSql::removeBudget");
startCommitUnit(__func__);
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmBudgetConfig"].deleteString());
q.bindValue(":id", bud.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting Budget")));
--m_budgets;
writeFileInfo();
endCommitUnit(__func__);
}
void MyMoneyStorageSql::writeBudget (const MyMoneyBudget& bud, MyMoneySqlQuery& q) {
DBG("*** Entering MyMoneyStorageSql::writeBudget");
TQDomDocument d; // create a dummy XML document
TQDomElement e = d.createElement("BUDGETS");
d.appendChild (e);
bud.writeXML(d, e); // write the XML to document
q.bindValue(":id", bud.id());
q.bindValue(":name", bud.name());
q.bindValue(":start", bud.budgetStart());
q.bindValue(":XML", d.toString());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing Budgets")));
}
void MyMoneyStorageSql::writeFileInfo() {
DBG("*** Entering MyMoneyStorageSql::writeFileInfo");
// we have no real way of knowing when these change, so re-write them every time
deleteKeyValuePairs("STORAGE", "");
writeKeyValuePairs("STORAGE", "", m_storage->pairs());
//
MyMoneySqlQuery q(this);
q.prepare ("SELECT * FROM kmmFileInfo;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, "checking fileinfo"));
TQString qs;
if (q.next())
qs = m_db.m_tables["kmmFileInfo"].updateString();
else
qs = (m_db.m_tables["kmmFileInfo"].insertString());
q.prepare(qs);
q.bindValue(":version", m_dbVersion);
q.bindValue(":fixLevel", m_storage->fileFixVersion());
q.bindValue(":created", TQString(m_storage->creationDate().toString(Qt::ISODate)));
//q.bindValue(":lastModified", m_storage->lastModificationDate().toString(Qt::ISODate));
q.bindValue(":lastModified", TQString(TQDate::currentDate().toString(Qt::ISODate)));
q.bindValue(":baseCurrency", m_storage->pairs()["kmm-baseCurrency"]);
q.bindValue(":institutions", (unsigned long long) m_institutions);
q.bindValue(":accounts", (unsigned long long) m_accounts);
q.bindValue(":payees", (unsigned long long) m_payees);
q.bindValue(":transactions", (unsigned long long) m_transactions);
q.bindValue(":splits", (unsigned long long) m_splits);
q.bindValue(":securities", (unsigned long long) m_securities);
q.bindValue(":prices", (unsigned long long) m_prices);
q.bindValue(":currencies", (unsigned long long) m_currencies);
q.bindValue(":schedules", (unsigned long long) m_schedules);
q.bindValue(":reports", (unsigned long long) m_reports);
q.bindValue(":kvps", (unsigned long long) m_kvps);
q.bindValue(":budgets", (unsigned long long) m_budgets);
q.bindValue(":dateRangeStart", TQDate());
q.bindValue(":dateRangeEnd", TQDate());
//FIXME: This modifies all m_<variable> used in this function.
// Sometimes the memory has been updated.
// Should most of these be tracked in a view?
// Variables actually needed are: version, fileFixVersion, creationDate,
// baseCurrency, encryption, update info, and logon info.
try {
//readFileInfo();
} catch (...) {
startCommitUnit(__func__);
}
q.bindValue(":hiInstitutionId", (unsigned long long) m_hiIdInstitutions);
q.bindValue(":hiPayeeId", (unsigned long long) m_hiIdPayees);
q.bindValue(":hiAccountId", (unsigned long long) m_hiIdAccounts);
q.bindValue(":hiTransactionId", (unsigned long long) m_hiIdTransactions);
q.bindValue(":hiScheduleId", (unsigned long long) m_hiIdSchedules);
q.bindValue(":hiSecurityId", (unsigned long long) m_hiIdSecurities);
q.bindValue(":hiReportId", (unsigned long long) m_hiIdReports);
q.bindValue(":hiBudgetId", (unsigned long long) m_hiIdBudgets);
q.bindValue(":encryptData", m_encryptData);
q.bindValue(":updateInProgress", "N");
q.bindValue(":logonUser", m_logonUser);
q.bindValue(":logonAt", m_logonAt.toString(Qt::ISODate));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing FileInfo")));
}
// **** Key/value pairs ****
void MyMoneyStorageSql::writeKeyValuePairs(const TQString& kvpType, const TQString& kvpId, const TQMap<TQString, TQString>& pairs) {
DBG("*** Entering MyMoneyStorageSql::writeKeyValuePairs");
TQMap<TQString, TQString>::const_iterator it;
for(it = pairs.begin(); it != pairs.end(); ++it) {
writeKeyValuePair (kvpType, kvpId, it.key(), it.data());
}
}
void MyMoneyStorageSql::writeKeyValuePair (const TQString& kvpType, const TQString& kvpId, const TQString& kvpKey, const TQString& kvpData) {
DBG("*** Entering MyMoneyStorageSql::writeKeyValuePair");
MyMoneySqlQuery q(this);
q.prepare (m_db.m_tables["kmmKeyValuePairs"].insertString());
q.bindValue(":kvpType", kvpType);
q.bindValue(":kvpId", kvpId);
q.bindValue(":kvpKey", kvpKey);
q.bindValue(":kvpData", kvpData);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("writing KVP")));
++m_kvps;
}
void MyMoneyStorageSql::deleteKeyValuePairs (const TQString& kvpType, const TQString& kvpId) {
DBG("*** Entering MyMoneyStorageSql::deleteKeyValuePairs");
MyMoneySqlQuery q(this);
q.prepare ("DELETE FROM kmmKeyValuePairs WHERE kvpType = :kvpType AND kvpId = :kvpId;");
q.bindValue(":kvpType", kvpType);
q.bindValue(":kvpId", kvpId);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("deleting kvp for %1 %2").arg(kvpType).arg(kvpId)));
m_kvps -= q.numRowsAffected();
}
//******************************** read SQL routines **************************************
#define CASE(a) if ((*ft)->name() == #a)
#define GETSTRING q.value(i).toString()
#define GETCSTRING q.value(i).toCString()
#define GETDATE getDate(GETSTRING)
#define GETDATETIME getDateTime(GETSTRING)
#define GETINT q.value(i).toInt()
#define GETULL q.value(i).toULongLong()
void MyMoneyStorageSql::readFileInfo(void) {
DBG("*** Entering MyMoneyStorageSql::readFileInfo");
signalProgress(0, 18, TQObject::tr("Loading file information..."));
MyMoneyDbTable& t = m_db.m_tables["kmmFileInfo"];
MyMoneySqlQuery q(this);
q.prepare (t.selectAllString());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading FileInfo")));
if (!q.next()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("retrieving FileInfo")));
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
while (ft != t.end()) {
// versioning is now handled in open routine
/* CASE(version) setVersion(GETSTRING); // check version == current version...
else*/
CASE(created) m_storage->setCreationDate(GETDATE);
else CASE(lastModified) m_storage->setLastModificationDate(GETDATE);
else CASE(hiInstitutionId) m_hiIdInstitutions = (unsigned long) GETULL;
else CASE(hiPayeeId) m_hiIdPayees = (unsigned long) GETULL;
else CASE(hiAccountId) m_hiIdAccounts = (unsigned long) GETULL;
else CASE(hiTransactionId) m_hiIdTransactions = (unsigned long) GETULL;
else CASE(hiScheduleId) m_hiIdSchedules = (unsigned long) GETULL;
else CASE(hiSecurityId) m_hiIdSecurities = (unsigned long) GETULL;
else CASE(hiReportId ) m_hiIdReports = (unsigned long) GETULL;
else CASE(hiBudgetId ) m_hiIdBudgets = (unsigned long) GETULL;
else CASE(institutions) m_institutions = (unsigned long) GETULL;
else CASE(accounts ) m_accounts = (unsigned long) GETULL;
else CASE(payees ) m_payees = (unsigned long) GETULL;
else CASE(transactions) m_transactions = (unsigned long) GETULL;
else CASE(splits ) m_splits = (unsigned long) GETULL;
else CASE(securities ) m_securities = (unsigned long) GETULL;
else CASE(currencies ) m_currencies = (unsigned long) GETULL;
else CASE(schedules ) m_schedules = (unsigned long) GETULL;
else CASE(prices ) m_prices = (unsigned long) GETULL;
else CASE(kvps ) m_kvps = (unsigned long) GETULL;
else CASE(reports ) m_reports = (unsigned long) GETULL;
else CASE(budgets ) m_budgets = (unsigned long) GETULL;
else CASE(encryptData) m_encryptData = GETSTRING;
else CASE(logonUser) m_logonUser = GETSTRING;
else CASE(logonAt) m_logonAt = GETDATETIME;
++ft; ++i;
signalProgress(i,0);
}
m_storage->setPairs(readKeyValuePairs("STORAGE", TQString("")).pairs());
}
/*void MyMoneyStorageSql::setVersion (const TQString& version) {
DBG("*** Entering MyMoneyStorageSql::setVersion");
m_dbVersion = version.section('.', 0, 0).toUInt();
m_minorVersion = version.section('.', 1, 1).toUInt();
// Okay, I made a cockup by forgetting to include a fixversion in the database
// design, so we'll use the minor version as fix level (similar to VERSION
// and FIXVERSION in XML file format). A second mistake was setting minor version to 1
// in the first place, so we need to subtract one on reading and add one on writing (sigh)!!
m_storage->setFileFixVersion( m_minorVersion - 1);
}*/
void MyMoneyStorageSql::readInstitutions(void) {
TRY
TQMap<TQString, MyMoneyInstitution> iList = fetchInstitutions();
m_storage->loadInstitutions(iList);
readFileInfo();
m_storage->loadInstitutionId(m_hiIdInstitutions);
PASS
}
const TQMap<TQString, MyMoneyInstitution> MyMoneyStorageSql::fetchInstitutions (const TQStringList& idList, bool forUpdate) const {
DBG("*** Entering MyMoneyStorageSql::readInstitutions");
signalProgress(0, m_institutions, TQObject::tr("Loading institutions..."));
int progress = 0;
TQMap<TQString, MyMoneyInstitution> iList;
unsigned long lastId = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmInstitutions"];
MyMoneySqlQuery sq(const_cast <MyMoneyStorageSql*> (this));
sq.prepare ("SELECT id from kmmAccounts where institutionId = :id");
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
TQString queryString (t.selectAllString(false));
// Use bind variables, instead of just inserting the values in the queryString,
// so that values containing a ':' will work.
if (! idList.empty()) {
queryString += " WHERE";
for (unsigned i = 0; i < idList.count(); ++i)
queryString += " id = :id" + TQString::number(i) + " OR";
queryString = queryString.left(queryString.length() - 2);
}
if (forUpdate)
queryString += " FOR UPDATE";
queryString += ";";
q.prepare (queryString);
if (! idList.empty()) {
TQStringList::const_iterator bindVal = idList.begin();
for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
q.bindValue (":id" + TQString::number(i), *bindVal);
}
}
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Institution")));
while (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
TQString iid;
MyMoneyInstitution inst;
while (ft != t.end()) {
CASE(id) iid = GETSTRING;
else CASE(name) inst.setName(GETSTRING);
else CASE(manager) inst.setManager(GETSTRING);
else CASE(routingCode) inst.setSortcode(GETSTRING);
else CASE(addressStreet) inst.setStreet(GETSTRING);
else CASE(addressCity) inst.setCity(GETSTRING);
else CASE(addressZipcode) inst.setPostcode(GETSTRING);
else CASE(telephone) inst.setTelephone(GETSTRING);
++ft; ++i;
}
// get list of subaccounts
sq.bindValue(":id", iid);
if (!sq.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Institution AccountList")));
TQStringList aList;
while (sq.next()) aList.append(sq.value(0).toString());
for (TQStringList::ConstIterator it = aList.begin(); it != aList.end(); ++it)
inst.addAccountId(*it);
iList[iid] = MyMoneyInstitution(iid, inst);
unsigned long id = extractId(iid);
if(id > lastId)
lastId = id;
signalProgress (++progress, 0);
}
return iList;
}
void MyMoneyStorageSql::readPayees (const TQString& id) {
DBG("*** Entering MyMoneyStorageSql::readPayees");
TQValueList<TQString> list;
list.append(id);
readPayees(list);
}
void MyMoneyStorageSql::readPayees(const TQValueList<TQString> pid) {
DBG("*** Entering MyMoneyStorageSql::readPayees");
TRY
TQStringList pidList;
tqCopy(pid.begin(), pid.end(), qBackInserter(pidList));
m_storage->loadPayees(fetchPayees(pidList));
readFileInfo();
m_storage->loadPayeeId(m_hiIdPayees);
CATCH
delete e; // ignore duplicates
ECATCH
// if (pid.isEmpty()) m_payeeListRead = true;
}
const TQMap<TQString, MyMoneyPayee> MyMoneyStorageSql::fetchPayees (const TQStringList& idList, bool /*forUpdate*/) const {
DBG("*** Entering MyMoneyStorageSql::readPayees");
if (m_displayStatus) {
signalProgress(0, m_payees, TQObject::tr("Loading payees..."));
} else {
// if (m_payeeListRead) return;
}
int progress = 0;
TQMap<TQString, MyMoneyPayee> pList;
//unsigned long lastId;
const MyMoneyDbTable& t = m_db.m_tables["kmmPayees"];
MyMoneyDbTable::field_iterator payeeEnd = t.end();
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
if (idList.isEmpty()) {
q.prepare (t.selectAllString());
} else {
TQString whereClause = " where (";
TQString itemConnector = "";
TQStringList::ConstIterator it;
for (it = idList.begin(); it != idList.end(); ++it) {
whereClause.append(TQString("%1id = '%2'").arg(itemConnector).arg(*it));
itemConnector = " or ";
}
whereClause += ")";
q.prepare (t.selectAllString(false) + whereClause);
}
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Payee")));
while (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
TQString pid;
TQString boolChar;
MyMoneyPayee payee;
unsigned int type;
bool ignoreCase;
TQString matchKeys;
while (ft != payeeEnd) {
CASE(id) pid = GETCSTRING;
else CASE(name) payee.setName(GETSTRING);
else CASE(reference) payee.setReference(GETSTRING);
else CASE(email) payee.setEmail(GETSTRING);
else CASE(addressStreet) payee.setAddress(GETSTRING);
else CASE(addressCity) payee.setCity(GETSTRING);
else CASE(addressZipcode) payee.setPostcode(GETSTRING);
else CASE(addressState) payee.setState(GETSTRING);
else CASE(telephone) payee.setTelephone(GETSTRING);
else CASE(notes) payee.setNotes(GETSTRING);
else CASE(defaultAccountId) payee.setDefaultAccountId(GETSTRING);
else CASE(matchData) type = GETINT;
else CASE(matchIgnoreCase) ignoreCase = (GETSTRING == "Y");
else CASE(matchKeys) matchKeys = GETSTRING;
++ft; ++i;
}
payee.setMatchData (static_cast<MyMoneyPayee::payeeMatchType>(type), ignoreCase, matchKeys);
if (pid == "USER") {
TRY
m_storage->setUser(payee);
PASS
} else {
pList[pid] = MyMoneyPayee(pid, payee);
//unsigned long id = extractId(TQString(pid));
//if(id > lastId)
// lastId = id;
}
if (m_displayStatus) signalProgress(++progress, 0);
}
return pList;
}
const TQMap<TQString, MyMoneyAccount> MyMoneyStorageSql::fetchAccounts (const TQStringList& idList, bool forUpdate) const {
DBG("*** Entering MyMoneyStorageSql::fetchAccounts");
signalProgress(0, m_accounts, TQObject::tr("Loading accounts..."));
int progress = 0;
TQMap<TQString, MyMoneyAccount> accList;
TQStringList kvpAccountList;
const MyMoneyDbTable& t = m_db.m_tables["kmmAccounts"];
MyMoneyDbTable::field_iterator accEnd = t.end();
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
MyMoneySqlQuery sq(const_cast <MyMoneyStorageSql*> (this));
TQString childQueryString = "SELECT id, parentId FROM kmmAccounts WHERE ";
TQString queryString (t.selectAllString(false));
// Use bind variables, instead of just inserting the values in the queryString,
// so that values containing a ':' will work.
if (! idList.empty()) {
kvpAccountList = idList;
queryString += " WHERE id IN (";
childQueryString += " parentId IN (";
for (unsigned i = 0; i < idList.count(); ++i) {
queryString += " :id" + TQString::number(i) + ", ";
childQueryString += ":id" + TQString::number(i) + ", ";
}
queryString = queryString.left(queryString.length() - 2) + ")";
childQueryString = childQueryString.left(childQueryString.length() - 2) + ")";
} else {
childQueryString += " NOT parentId IS NULL";
}
queryString += " ORDER BY id";
childQueryString += " ORDER BY parentid, id";
if (forUpdate) {
queryString += " FOR UPDATE";
childQueryString += " FOR UPDATE";
}
q.prepare (queryString);
sq.prepare (childQueryString);
if (! idList.empty()) {
TQStringList::const_iterator bindVal = idList.begin();
for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
q.bindValue (":id" + TQString::number(i), *bindVal);
sq.bindValue (":id" + TQString::number(i), *bindVal);
}
}
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Account")));
if (!sq.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading subAccountList")));
while (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
TQString aid;
TQString balance;
MyMoneyAccount acc;
while (ft != accEnd) {
CASE(id) aid = GETCSTRING;
else CASE(institutionId) acc.setInstitutionId(GETCSTRING);
else CASE(parentId) acc.setParentAccountId(GETCSTRING);
else CASE(lastReconciled) acc.setLastReconciliationDate(GETDATE);
else CASE(lastModified) acc.setLastModified(GETDATE);
else CASE(openingDate) acc.setOpeningDate(GETDATE);
else CASE(accountNumber) acc.setNumber(GETSTRING);
else CASE(accountType) acc.setAccountType(static_cast<MyMoneyAccount::accountTypeE>(GETINT));
else CASE(accountName) acc.setName(GETSTRING);
else CASE(description) acc.setDescription(GETSTRING);
else CASE(currencyId) acc.setCurrencyId(GETCSTRING);
else CASE(balance) acc.setBalance(GETSTRING);
else CASE(transactionCount)
const_cast <MyMoneyStorageSql*> (this)->m_transactionCountMap[aid] = (unsigned long) GETULL;
++ft; ++i;
}
// Process any key value pair
if (idList.empty())
kvpAccountList.append(aid);
// in database mode, load the balance from the account record
// else we would need to read all the transactions
accList.insert(aid, MyMoneyAccount(aid, acc));
if (acc.value("PreferredAccount") == "Yes") {
const_cast <MyMoneyStorageSql*> (this)->m_preferred.addAccount(aid);
}
signalProgress(++progress, 0);
}
TQMapIterator<TQString, MyMoneyAccount> it_acc;
TQMapIterator<TQString, MyMoneyAccount> accListEnd = accList.end();
while (sq.next()) {
it_acc = accList.find(sq.value(1).toString());
if (it_acc != accListEnd && it_acc.data().id() == sq.value(1).toString()) {
while (sq.isValid() && it_acc != accListEnd
&& it_acc.data().id() == sq.value(1).toString()) {
it_acc.data().addAccountId(sq.value(0).toString());
sq.next();
}
sq.prev();
}
}
//TODO: There should be a better way than this. What's below is O(n log n) or more,
// where it may be able to be done in O(n), if things are just right.
// The operator[] call in the loop is the most expensive call in this function, according
// to several profile runs.
TQMap <TQString, MyMoneyKeyValueContainer> kvpResult = readKeyValuePairs("ACCOUNT", kvpAccountList);
TQMap <TQString, MyMoneyKeyValueContainer>::const_iterator kvp_end = kvpResult.end();
for (TQMap <TQString, MyMoneyKeyValueContainer>::const_iterator it_kvp = kvpResult.begin();
it_kvp != kvp_end; ++it_kvp) {
accList[it_kvp.key()].setPairs(it_kvp.data().pairs());
}
kvpResult = readKeyValuePairs("ONLINEBANKING", kvpAccountList);
kvp_end = kvpResult.end();
for (TQMap <TQString, MyMoneyKeyValueContainer>::const_iterator it_kvp = kvpResult.begin();
it_kvp != kvp_end; ++it_kvp) {
accList[it_kvp.key()].setOnlineBankingSettings(it_kvp.data());
}
return accList;
}
void MyMoneyStorageSql::readAccounts(void) {
m_storage->loadAccounts(fetchAccounts());
m_storage->loadAccountId(m_hiIdAccounts);
}
const TQMap<TQString, MyMoneyMoney> MyMoneyStorageSql::fetchBalance(const TQStringList& idList, const TQDate& date) const {
TQMap<TQString, MyMoneyMoney> returnValue;
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
TQString queryString = "SELECT action, shares, accountId, postDate "
"FROM kmmSplits WHERE txType = 'N' AND accountId in (";
for (unsigned i = 0; i < idList.count(); ++i) {
queryString += " :id" + TQString::number(i) + ", ";
}
queryString = queryString.left(queryString.length() - 2) + " )";
// SQLite stores dates as YYYY-MM-DDTHH:mm:ss with 0s for the time part. This makes
// the <= operator misbehave when the date matches. To avoid this, add a day to the
// requested date and use the < operator.
if (date.isValid() && !date.isNull())
queryString += TQString(" AND postDate < '%1'").arg(date.addDays(1).toString(Qt::ISODate));
DBG (queryString);
q.prepare(queryString);
TQStringList::const_iterator bindVal = idList.begin();
for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
q.bindValue (":id" + TQString::number(i), *bindVal);
returnValue[*bindVal] = MyMoneyMoney(0);
}
if (!q.exec())
throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("fetching balance")));
TQString id;
TQString shares;
TQString action;
while (q.next()) {
id = q.value(2).toString();
shares = q.value(1).toString();
action = q.value(0).toString();
if (MyMoneySplit::ActionSplitShares == action)
returnValue[id] = returnValue[id] * MyMoneyMoney(shares);
else
returnValue[id] += MyMoneyMoney(shares);
}
return returnValue;
}
void MyMoneyStorageSql::readTransactions(const TQString& tidList, const TQString& dateClause) {
TRY
m_storage->loadTransactions(fetchTransactions(tidList, dateClause));
m_storage->loadTransactionId(m_hiIdTransactions);
PASS
}
void MyMoneyStorageSql::readTransactions(const MyMoneyTransactionFilter& filter) {
TRY
m_storage->loadTransactions(fetchTransactions(filter));
m_storage->loadTransactionId(m_hiIdTransactions);
PASS
}
const TQMap<TQString, MyMoneyTransaction> MyMoneyStorageSql::fetchTransactions (const TQString& tidList, const TQString& dateClause, bool /*forUpdate*/) const {
DBG("*** Entering MyMoneyStorageSql::readTransactions");
// if (m_transactionListRead) return; // all list already in memory
if (m_displayStatus) signalProgress(0, m_transactions, TQObject::tr("Loading transactions..."));
int progress = 0;
// m_payeeList.clear();
TQString whereClause;
whereClause = " WHERE txType = 'N' ";
if (! tidList.isEmpty()) {
whereClause += " AND id IN " + tidList;
}
if (!dateClause.isEmpty()) whereClause += " and " + dateClause;
const MyMoneyDbTable& t = m_db.m_tables["kmmTransactions"];
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
q.prepare (t.selectAllString(false) + whereClause + " ORDER BY id;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Transaction")));
const MyMoneyDbTable& ts = m_db.m_tables["kmmSplits"];
whereClause = " WHERE txType = 'N' ";
if (! tidList.isEmpty()) {
whereClause += " AND transactionId IN " + tidList;
}
if (!dateClause.isEmpty()) whereClause += " and " + dateClause;
MyMoneySqlQuery qs(const_cast <MyMoneyStorageSql*> (this));
TQString splitQuery = ts.selectAllString(false) + whereClause
+ " ORDER BY transactionId, splitId;";
qs.prepare (splitQuery);
if (!qs.exec()) throw new MYMONEYEXCEPTION(buildError (qs, __func__, "reading Splits"));
TQString splitTxId = "ZZZ";
MyMoneySplit s;
if (qs.next()) {
splitTxId = qs.value(0).toString();
readSplit (s, qs, ts);
} else {
splitTxId = "ZZZ";
}
TQMap <TQString, MyMoneyTransaction> txMap;
TQStringList txList;
MyMoneyDbTable::field_iterator txEnd = t.end();
while (q.next()) {
MyMoneyTransaction tx;
TQString txId;
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
while (ft != txEnd) {
CASE(id) txId = GETSTRING;
else CASE(postDate) tx.setPostDate(GETDATE);
else CASE(memo) tx.setMemo(GETSTRING);
else CASE(entryDate) tx.setEntryDate(GETDATE);
else CASE(currencyId) tx.setCommodity(GETCSTRING);
else CASE(bankId) tx.setBankID(GETSTRING);
++ft; ++i;
}
while (txId < splitTxId && splitTxId != "ZZZ") {
if (qs.next()) {
splitTxId = qs.value(0).toString();
readSplit (s, qs, ts);
} else {
splitTxId = "ZZZ";
}
}
while (txId == splitTxId) {
tx.addSplit (s);
if (qs.next()) {
splitTxId = qs.value(0).toString();
readSplit (s, qs, ts);
} else {
splitTxId = "ZZZ";
}
}
// Process any key value pair
if (! txId.isEmpty()) {
txList.append(txId);
tx = MyMoneyTransaction(txId, tx);
txMap.insert(tx.uniqueSortKey(), tx);
}
}
TQMap <TQString, MyMoneyKeyValueContainer> kvpMap = readKeyValuePairs("TRANSACTION", txList);
TQMap<TQString, MyMoneyTransaction> tList;
TQMapIterator<TQString, MyMoneyTransaction> txMapEnd = txMap.end();
for (TQMapIterator<TQString, MyMoneyTransaction> i = txMap.begin();
i != txMapEnd; ++i) {
i.data().setPairs(kvpMap[i.data().id()].pairs());
if (m_displayStatus) signalProgress(++progress, 0);
}
if ((tidList.isEmpty()) && (dateClause.isEmpty())) {
//tqDebug("setting full list read");
}
return txMap;
}
int MyMoneyStorageSql::splitState(const MyMoneyTransactionFilter::stateOptionE& state) const
{
int rc = MyMoneySplit::NotReconciled;
switch(state) {
default:
case MyMoneyTransactionFilter::notReconciled:
break;
case MyMoneyTransactionFilter::cleared:
rc = MyMoneySplit::Cleared;
break;
case MyMoneyTransactionFilter::reconciled:
rc = MyMoneySplit::Reconciled;
break;
case MyMoneyTransactionFilter::frozen:
rc = MyMoneySplit::Frozen;
break;
}
return rc;
}
const TQMap<TQString, MyMoneyTransaction> MyMoneyStorageSql::fetchTransactions (const MyMoneyTransactionFilter& filter) const {
DBG("*** Entering MyMoneyStorageSql::readTransactions");
// analyze the filter
// if (m_transactionListRead) return; // all list already in memory
// if the filter is restricted to certain accounts/categories
// check if we already have them all in memory
TQStringList accounts;
TQString inQuery;
filter.accounts(accounts);
filter.categories(accounts);
// TQStringList::iterator it;
// bool allAccountsLoaded = true;
// for (it = accounts.begin(); it != accounts.end(); ++it) {
// if (m_accountsLoaded.find(*it) == m_accountsLoaded.end()) {
// allAccountsLoaded = false;
// break;
// }
// }
// if (allAccountsLoaded) return;
/* Some filter combinations do not lend themselves to implementation
* in SQL, or are likely to require such extensive reading of the database
* as to make it easier to just read everything into memory. */
bool canImplementFilter = true;
MyMoneyMoney m1, m2;
if (filter.amountFilter( m1, m2 )) {
alert ("Amount Filter Set");
canImplementFilter = false;
}
TQString n1, n2;
if (filter.numberFilter(n1, n2)) {
alert("Number filter set");
canImplementFilter = false;
}
int t1;
if (filter.firstType(t1)) {
alert("Type filter set");
canImplementFilter = false;
}
// int s1;
// if (filter.firstState(s1)) {
// alert("State filter set");
// canImplementFilter = false;
// }
TQRegExp t2;
if (filter.textFilter(t2)) {
alert("text filter set");
canImplementFilter = false;
}
MyMoneyTransactionFilter::FilterSet s = filter.filterSet();
if (s.singleFilter.validityFilter) {
alert("Validity filter set");
canImplementFilter = false;
}
if (!canImplementFilter) {
TQMap<TQString, MyMoneyTransaction> transactionList = fetchTransactions();
TQMap<TQString, MyMoneyTransaction>::ConstIterator it_t;
TQMap<TQString, MyMoneyTransaction>::ConstIterator txListEnd = transactionList.end();
std::remove_if(transactionList.begin(), transactionList.end(), FilterFail(filter, m_storagePtr));
return transactionList;
}
bool accountsOnlyFilter = true;
bool splitFilterActive = false; // the split filter is active if we are selecting on fields in the split table
// get start and end dates
TQDate start = filter.fromDate();
TQDate end = filter.toDate();
// not entirely sure if the following is correct, but at best, saves a lot of reads, at worst
// it only causes us to read a few more transactions that strictly necessary (I think...)
if (start == KMyMoneySettings::startDate().date()) start = TQDate();
bool txFilterActive = ((start != TQDate()) || (end != TQDate())); // and this for fields in the transaction table
if (txFilterActive) accountsOnlyFilter = false;
TQString whereClause = "";
TQString subClauseconnector = " where txType = 'N' and ";
// payees
TQStringList payees;
//filter.payees(payees);
if (filter.payees(payees)) {
accountsOnlyFilter = false;
TQString itemConnector = "payeeId in (";
TQString payeesClause = "";
TQStringList::const_iterator it;
for (it = payees.begin(); it != payees.end(); ++it) {
payeesClause.append(TQString("%1'%2'")
.arg(itemConnector).arg(*it));
itemConnector = ", ";
}
if (!payeesClause.isEmpty()) {
whereClause += subClauseconnector + payeesClause + ")";
subClauseconnector = " and ";
}
splitFilterActive = true;
}
// accounts and categories
if (!accounts.isEmpty()) {
splitFilterActive = true;
TQString itemConnector = "accountId in (";
TQString accountsClause = "";
TQStringList::const_iterator it;
for (it = accounts.begin(); it != accounts.end(); ++it) {
// if (m_accountsLoaded.find(*it) == m_accountsLoaded.end()) {
accountsClause.append(TQString("%1 '%2'")
.arg(itemConnector).arg(*it));
itemConnector = ", ";
//if (accountsOnlyFilter) m_accountsLoaded.append(*it); // a bit premature...
// }
}
if (!accountsClause.isEmpty()) {
whereClause += subClauseconnector + accountsClause + ")";
subClauseconnector = " and (";
}
}
// split states
TQValueList <int> splitStates;
if (filter.states(splitStates)) {
splitFilterActive = true;
TQString itemConnector = " reconcileFlag IN (";
TQString statesClause = "";
for (TQValueList<int>::ConstIterator it = splitStates.begin(); it != splitStates.end(); ++it) {
statesClause.append(TQString(" %1 '%2'")
.arg(itemConnector)
.arg(splitState(MyMoneyTransactionFilter::stateOptionE(*it))));
itemConnector = ",";
}
if (!statesClause.isEmpty()) {
whereClause += subClauseconnector + statesClause + ")";
subClauseconnector = " and (";
}
}
// I've given up trying to work out the logic. we keep getting the wrong number of close brackets
int obc = whereClause.contains('(');
int cbc = whereClause.contains(')');
if (cbc > obc) {
tqFatal("invalid where clause - %s", whereClause.latin1());
}
while (cbc < obc) {
whereClause.append(")");
cbc++;
}
// if the split filter is active, but the where clause is empty
// it means we already have all the transactions for the specified filter
// in memory, so just exit
if ((splitFilterActive) && (whereClause.isEmpty())) {
tqDebug("all transactions already in storage");
return fetchTransactions();
}
// if we have neither a split filter, nor a tx (date) filter
// it's effectively a read all
if ((!splitFilterActive) && (!txFilterActive)) {
//tqDebug("reading all transactions");
return fetchTransactions();
}
// build a date clause for the transaction table
TQString dateClause;
TQString connector = "";
if (end != TQDate()) {
dateClause = TQString("(postDate < '%1')").arg(end.addDays(1).toString(Qt::ISODate));
connector = " and ";
}
if (start != TQDate()) {
dateClause += TQString("%1 (postDate >= '%2')").arg(connector).arg(start.toString(Qt::ISODate));
}
// now get a list of transaction ids
// if we have only a date filter, we need to build the list from the tx table
// otherwise we need to build from the split table
if (splitFilterActive) {
inQuery = TQString("(select distinct transactionId from kmmSplits %1)").arg(whereClause);
} else {
inQuery = TQString("(select distinct id from kmmTransactions where %1)").arg(dateClause);
txFilterActive = false; // kill off the date filter now
}
return fetchTransactions(inQuery, dateClause);
//FIXME: if we have an accounts-only filter, recalc balances on loaded accounts
}
unsigned long MyMoneyStorageSql::transactionCount (const TQString& aid) const {
DBG("*** Entering MyMoneyStorageSql::transactionCount");
if (aid.length() == 0)
return m_transactions;
else
return m_transactionCountMap[aid];
}
void MyMoneyStorageSql::readSplit (MyMoneySplit& s, const MyMoneySqlQuery& q, const MyMoneyDbTable& t) const {
DBG("*** Entering MyMoneyStorageSql::readSplit");
s.clearId();
MyMoneyDbTable::field_iterator ft = t.begin();
MyMoneyDbTable::field_iterator splitEnd = t.end();
int i = 0;
// Use the TQString here instead of CASE, since this is called so often.
TQString fieldName;
while (ft != splitEnd) {
fieldName = (*ft)->name();
if (fieldName == "payeeId") s.setPayeeId(GETCSTRING);
else if (fieldName == "reconcileDate") s.setReconcileDate(GETDATE);
else if (fieldName == "action") s.setAction(GETCSTRING);
else if (fieldName == "reconcileFlag") s.setReconcileFlag(static_cast<MyMoneySplit::reconcileFlagE>(GETINT));
else if (fieldName == "value") s.setValue(MyMoneyMoney(TQStringEmpty(GETSTRING)));
else if (fieldName == "shares") s.setShares(MyMoneyMoney(TQStringEmpty(GETSTRING)));
else if (fieldName == "price") s.setPrice(MyMoneyMoney(TQStringEmpty(GETSTRING)));
else if (fieldName == "memo") s.setMemo(GETSTRING);
else if (fieldName == "accountId") s.setAccountId(GETCSTRING);
else if (fieldName == "checkNumber") s.setNumber(GETSTRING);
//else if (fieldName == "postDate") s.setPostDate(GETDATETIME); // FIXME - when Tom puts date into split object
else if (fieldName == "bankId") s.setBankID(GETSTRING);
++ft; ++i;
}
return;
}
bool MyMoneyStorageSql::isReferencedByTransaction(const TQString& id) const {
DBG("*** Entering MyMoneyStorageSql::isReferencedByTransaction");
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
q.prepare("SELECT COUNT(*) FROM kmmTransactions "
"INNER JOIN kmmSplits ON kmmTransactions.id = kmmSplits.transactionId "
"WHERE kmmTransactions.currencyId = :ID OR kmmSplits.payeeId = :ID "
"OR kmmSplits.accountId = :ID");
q.bindValue(":ID", id);
if ((!q.exec()) || (!q.next())) {
buildError (q, __func__, "error retrieving reference count");
tqFatal("Error retrieving reference count"); // definitely shouldn't happen
}
return (0 != q.value(0).toULongLong());
}
void MyMoneyStorageSql::readSchedules(void) {
TRY
m_storage->loadSchedules(fetchSchedules());
readFileInfo();
m_storage->loadScheduleId(m_hiIdSchedules);
PASS
}
const TQMap<TQString, MyMoneySchedule> MyMoneyStorageSql::fetchSchedules (const TQStringList& idList, bool forUpdate) const {
DBG("*** Entering MyMoneyStorageSql::readSchedules");
signalProgress(0, m_schedules, TQObject::tr("Loading schedules..."));
int progress = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmSchedules"];
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
TQMap<TQString, MyMoneySchedule> sList;
//unsigned long lastId = 0;
const MyMoneyDbTable& ts = m_db.m_tables["kmmSplits"];
MyMoneySqlQuery qs(const_cast <MyMoneyStorageSql*> (this));
qs.prepare (ts.selectAllString(false) + " WHERE transactionId = :id ORDER BY splitId;");
MyMoneySqlQuery sq(const_cast <MyMoneyStorageSql*> (this));
sq.prepare ("SELECT payDate from kmmSchedulePaymentHistory where schedId = :id");
TQString queryString (t.selectAllString(false));
// Use bind variables, instead of just inserting the values in the queryString,
// so that values containing a ':' will work.
if (! idList.empty()) {
queryString += " WHERE";
for (unsigned i = 0; i < idList.count(); ++i)
queryString += " id = :id" + TQString::number(i) + " OR";
queryString = queryString.left(queryString.length() - 2);
}
queryString += " ORDER BY id;";
if (forUpdate)
queryString += " FOR UPDATE";
queryString += ";";
q.prepare (queryString);
if (! idList.empty()) {
TQStringList::const_iterator bindVal = idList.begin();
for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
q.bindValue (":id" + TQString::number(i), *bindVal);
}
}
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Schedules")));
while (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
MyMoneySchedule s;
TQString sId;
TQString boolChar;
TQDate nextPaymentDue;
while (ft != t.end()) {
CASE(id) sId = GETCSTRING;
else CASE(name) s.setName (GETSTRING);
else CASE(type) s.setType (static_cast<MyMoneySchedule::typeE>(GETINT));
else CASE(occurence) s.setOccurencePeriod (static_cast<MyMoneySchedule::occurenceE>(GETINT));
else CASE(occurenceMultiplier) s.setOccurenceMultiplier (GETINT);
else CASE(paymentType) s.setPaymentType (static_cast<MyMoneySchedule::paymentTypeE>(GETINT));
else CASE(startDate) s.setStartDate (GETDATE);
else CASE(endDate) s.setEndDate (GETDATE);
else CASE(fixed) {boolChar = GETSTRING; s.setFixed (boolChar == "Y");}
else CASE(autoEnter) {boolChar = GETSTRING; s.setAutoEnter (boolChar == "Y");}
else CASE(lastPayment) s.setLastPayment (GETDATE);
else CASE(weekendOption)
s.setWeekendOption (static_cast<MyMoneySchedule::weekendOptionE>(GETINT));
else CASE(nextPaymentDue) nextPaymentDue = GETDATE;
++ft; ++i;
}
// convert simple occurence to compound occurence
int mult = s.occurenceMultiplier();
MyMoneySchedule::occurenceE occ = s.occurencePeriod();
MyMoneySchedule::simpleToCompoundOccurence(mult,occ);
s.setOccurencePeriod(occ);
s.setOccurenceMultiplier(mult);
// now assign the id to the schedule
MyMoneySchedule _s(sId, s);
s = _s;
// read the associated transaction
// m_payeeList.clear();
const MyMoneyDbTable& t = m_db.m_tables["kmmTransactions"];
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
q.prepare (t.selectAllString(false) + " WHERE id = :id;");
q.bindValue(":id", s.id());
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Scheduled Transaction")));
if (!q.next()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("retrieving scheduled transaction")));
MyMoneyTransaction tx(s.id(), MyMoneyTransaction());
ft = t.begin();
i = 0;
while (ft != t.end()) {
CASE(postDate) tx.setPostDate(GETDATE);
else CASE(memo) tx.setMemo(GETSTRING);
else CASE(entryDate) tx.setEntryDate(GETDATE);
else CASE(currencyId) tx.setCommodity(GETCSTRING);
else CASE(bankId) tx.setBankID(GETSTRING);
++ft; ++i;
}
qs.bindValue(":id", s.id());
if (!qs.exec()) throw new MYMONEYEXCEPTION(buildError (qs, __func__, "reading Scheduled Splits"));
while (qs.next()) {
MyMoneySplit sp;
readSplit (sp, qs, ts);
tx.addSplit (sp);
}
// if (!m_payeeList.isEmpty())
// readPayees(m_payeeList);
// Process any key value pair
tx.setPairs(readKeyValuePairs("TRANSACTION", s.id()).pairs());
// If the transaction doesn't have a post date, setTransaction will reject it.
// The old way of handling things was to store the next post date in the schedule object
// and set the transaction post date to TQDate().
// For compatibility, if this is the case, copy the next post date from the schedule object
// to the transaction object post date.
if (!tx.postDate().isValid()) {
tx.setPostDate(nextPaymentDue);
}
s.setTransaction(tx);
// read in the recorded payments
sq.bindValue(":id", s.id());
if (!sq.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading schedule payment history")));
while (sq.next()) s.recordPayment (sq.value(0).toDate());
sList[s.id()] = s;
//FIXME: enable when schedules have KVPs.
// s.setPairs(readKeyValuePairs("SCHEDULE", s.id()).pairs());
//unsigned long id = extractId(s.id().data());
//if(id > lastId)
// lastId = id;
signalProgress(++progress, 0);
}
return sList;
}
void MyMoneyStorageSql::readSecurities(void) {
TRY
m_storage->loadSecurities(fetchSecurities());
readFileInfo();
m_storage->loadSecurityId(m_hiIdSecurities);
PASS
}
const TQMap<TQString, MyMoneySecurity> MyMoneyStorageSql::fetchSecurities (const TQStringList& /*idList*/, bool /*forUpdate*/) const {
DBG("*** Entering MyMoneyStorageSql::readSecurities");
signalProgress(0, m_securities, TQObject::tr("Loading securities..."));
int progress = 0;
TQMap<TQString, MyMoneySecurity> sList;
unsigned long lastId = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmSecurities"];
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
q.prepare (t.selectAllString(false) + " ORDER BY id;");
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Securities")));
while (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
MyMoneySecurity e;
TQString eid;
int saf = 0;
while (ft != t.end()) {
CASE(id) eid = GETSTRING;
else CASE(name) e.setName(GETSTRING);
else CASE(symbol) e.setTradingSymbol(GETSTRING);
else CASE(type) e.setSecurityType(static_cast<MyMoneySecurity::eSECURITYTYPE>(GETINT));
else CASE(smallestAccountFraction) saf = GETINT;
else CASE(tradingCurrency) e.setTradingCurrency(GETCSTRING);
else CASE(tradingMarket) e.setTradingMarket(GETSTRING);
++ft; ++i;
}
if(e.tradingCurrency().isEmpty())
e.setTradingCurrency(m_storage->pairs()["kmm-baseCurrency"]);
if(saf == 0)
saf = 100;
e.setSmallestAccountFraction(saf);
// Process any key value pairs
e.setPairs(readKeyValuePairs("SECURITY", eid).pairs());
//tell the storage objects we have a new security object.
// FIXME: Adapt to new interface make sure, to take care of the currencies as well
// see MyMoneyStorageXML::readSecurites()
MyMoneySecurity security(eid,e);
sList[security.id()] = security;
unsigned long id = extractId(security.id());
if(id > lastId)
lastId = id;
signalProgress(++progress, 0);
}
return sList;
}
void MyMoneyStorageSql::readPrices(void) {
TRY
// m_storage->addPrice(MyMoneyPrice(from, to, date, rate, source));
PASS
}
const MyMoneyPrice MyMoneyStorageSql::fetchSinglePrice (const TQString& fromIdList, const TQString& toIdList, const TQDate& date_, bool exactDate, bool /*forUpdate*/) const {
DBG("*** Entering MyMoneyStorageSql::fetchSinglePrice");
const MyMoneyDbTable& t = m_db.m_tables["kmmPrices"];
MyMoneyDbTable::field_iterator tableEnd = t.end();
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
TQString queryString = t.selectAllString(false);
// Use bind variables, instead of just inserting the values in the queryString,
// so that values containing a ':' will work.
// See balance query for why the date logic seems odd.
queryString += " WHERE fromId = :fromId AND toId = :toId AND priceDate < :priceDate ";
if (exactDate)
queryString += "AND priceDate > :exactDate ";
queryString += "ORDER BY priceDate DESC;";
q.prepare(queryString);
TQDate date (date_);
if(!date.isValid())
date = TQDate::currentDate();
q.bindValue(":fromId", fromIdList);
q.bindValue(":toId", toIdList);
q.bindValue(":priceDate", TQString(date.addDays(1).toString(Qt::ISODate)));
if (exactDate)
q.bindValue(":exactDate", TQString(date.toString(Qt::ISODate)));
if (! q.exec()) {}
if (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
TQString from;
TQString to;
TQDate date;
MyMoneyMoney rate;
TQString source;
bool foundFromId = false;
bool foundToId = false;
bool foundPriceDate = false;
bool foundPrice = false;
bool foundPriceSource = false;
while (ft != tableEnd) {
bool foundSomething = false;
if (!foundFromId && !foundSomething) {
CASE(fromId) {from = GETCSTRING; foundFromId = true; foundSomething = true;}
}
if (!foundToId && !foundSomething) {
CASE(toId) {to = GETCSTRING; foundToId = true; foundSomething = true;}
}
if (!foundPriceDate && !foundSomething) {
CASE(priceDate) {date = GETDATE; foundPriceDate = true; foundSomething = true;}
}
if (!foundPrice && !foundSomething) {
CASE(price) {rate = GETSTRING; foundPrice = true; foundSomething = true;}
}
if (!foundPriceSource && !foundSomething) {
CASE(priceSource) {source = GETSTRING; foundPriceSource = true; foundSomething = true;}
}
++ft; ++i;
}
return MyMoneyPrice(fromIdList, toIdList, date, rate, source);
}
return MyMoneyPrice();
}
const MyMoneyPriceList MyMoneyStorageSql::fetchPrices (const TQStringList& fromIdList, const TQStringList& toIdList, bool forUpdate) const {
DBG("*** Entering MyMoneyStorageSql::readPrices");
signalProgress(0, m_prices, TQObject::tr("Loading prices..."));
int progress = 0;
const_cast <MyMoneyStorageSql*> (this)->m_readingPrices = true;
MyMoneyPriceList pList;
const MyMoneyDbTable& t = m_db.m_tables["kmmPrices"];
MyMoneyDbTable::field_iterator tableEnd = t.end();
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
TQString queryString = t.selectAllString(false);
// Use bind variables, instead of just inserting the values in the queryString,
// so that values containing a ':' will work.
if (! fromIdList.empty()) {
queryString += " WHERE (";
for (unsigned i = 0; i < fromIdList.count(); ++i) {
queryString += " fromId = :fromId" + TQString::number(i) + " OR";
}
queryString = queryString.left(queryString.length() - 2) + ")";
}
if (! toIdList.empty()) {
queryString += " AND (";
for (unsigned i = 0; i < toIdList.count(); ++i) {
queryString += " toId = :toId" + TQString::number(i) + " OR";
}
queryString = queryString.left(queryString.length() - 2) + ")";
}
if (forUpdate)
queryString += " FOR UPDATE";
queryString += ";";
q.prepare (queryString);
if (! fromIdList.empty()) {
TQStringList::const_iterator bindVal = fromIdList.begin();
for (int i = 0; bindVal != fromIdList.end(); ++i, ++bindVal) {
q.bindValue (":fromId" + TQString::number(i), *bindVal);
}
}
if (! toIdList.empty()) {
TQStringList::const_iterator bindVal = toIdList.begin();
for (int i = 0; bindVal != toIdList.end(); ++i, ++bindVal) {
q.bindValue (":toId" + TQString::number(i), *bindVal);
}
}
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Prices")));
while (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
TQString from;
TQString to;
TQDate date;
MyMoneyMoney rate;
TQString source;
while (ft != tableEnd) {
CASE(fromId) from = GETCSTRING;
else CASE(toId) to = GETCSTRING;
else CASE(priceDate) date = GETDATE;
else CASE(price) rate = GETSTRING;
else CASE(priceSource) source = GETSTRING;
++ft; ++i;
}
pList [MyMoneySecurityPair(from, to)].insert(date, MyMoneyPrice(from, to, date, rate, source));
signalProgress(++progress, 0);
}
const_cast <MyMoneyStorageSql*> (this)->m_readingPrices = false;
return pList;
}
void MyMoneyStorageSql::readCurrencies(void) {
TRY
m_storage->loadCurrencies(fetchCurrencies());
PASS
}
const TQMap<TQString, MyMoneySecurity> MyMoneyStorageSql::fetchCurrencies (const TQStringList& idList, bool forUpdate) const {
DBG("*** Entering MyMoneyStorageSql::readCurrencies");
signalProgress(0, m_currencies, TQObject::tr("Loading currencies..."));
int progress = 0;
TQMap<TQString, MyMoneySecurity> cList;
const MyMoneyDbTable& t = m_db.m_tables["kmmCurrencies"];
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
TQString queryString (t.selectAllString(false));
// Use bind variables, instead of just inserting the values in the queryString,
// so that values containing a ':' will work.
if (! idList.empty()) {
queryString += " WHERE";
for (unsigned i = 0; i < idList.count(); ++i)
queryString += " isocode = :id" + TQString::number(i) + " OR";
queryString = queryString.left(queryString.length() - 2);
}
queryString += " ORDER BY ISOcode";
if (forUpdate)
queryString += " FOR UPDATE";
queryString += ";";
q.prepare (queryString);
if (! idList.empty()) {
TQStringList::const_iterator bindVal = idList.begin();
for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
q.bindValue (":id" + TQString::number(i), *bindVal);
}
}
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Currencies")));
while (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
TQString id;
MyMoneySecurity c;
TQChar symbol[3];
while (ft != t.end()) {
CASE(ISOcode) id = GETCSTRING;
else CASE(name) c.setName(GETSTRING);
else CASE(type) c.setSecurityType(static_cast<MyMoneySecurity::eSECURITYTYPE>(GETINT));
else CASE(symbol1) symbol[0] = TQChar(GETINT);
else CASE(symbol2) symbol[1] = TQChar(GETINT);
else CASE(symbol3) symbol[2] = TQChar(GETINT);
else CASE(partsPerUnit) c.setPartsPerUnit(GETINT);
else CASE(smallestCashFraction) c.setSmallestCashFraction(GETINT);
else CASE(smallestAccountFraction) c.setSmallestAccountFraction(GETINT);
++ft; ++i;
}
c.setTradingSymbol(TQString(symbol, 3).stripWhiteSpace());
cList[id] = MyMoneySecurity(id, c);
signalProgress(++progress, 0);
}
return cList;
}
void MyMoneyStorageSql::readReports(void) {
TRY
m_storage->loadReports(fetchReports());
readFileInfo();
m_storage->loadReportId(m_hiIdReports);
PASS
}
const TQMap<TQString, MyMoneyReport> MyMoneyStorageSql::fetchReports (const TQStringList& /*idList*/, bool /*forUpdate*/) const {
DBG("*** Entering MyMoneyStorageSql::readReports");
signalProgress(0, m_reports, TQObject::tr("Loading reports..."));
int progress = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmReportConfig"];
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
q.prepare (t.selectAllString(true));
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading reports")));
TQMap<TQString, MyMoneyReport> rList;
while (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
TQDomDocument d;
while (ft != t.end()) {
CASE(XML) d.setContent(GETSTRING, false);
++ft; ++i;
}
TQDomNode child = d.firstChild();
child = child.firstChild();
MyMoneyReport report;
if (report.read(child.toElement()))
rList[report.id()] = report;
signalProgress(++progress, 0);
}
return rList;
}
const TQMap<TQString, MyMoneyBudget> MyMoneyStorageSql::fetchBudgets (const TQStringList& idList, bool forUpdate) const {
DBG("*** Entering MyMoneyStorageSql::readBudgets");
signalProgress(0, m_budgets, TQObject::tr("Loading budgets..."));
int progress = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmBudgetConfig"];
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
TQString queryString (t.selectAllString(false));
if (! idList.empty()) {
queryString += " WHERE id = '" + idList.join("' OR id = '") + "'";
}
if (forUpdate)
queryString += " FOR UPDATE";
queryString += ";";
q.prepare (queryString);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading budgets")));
TQMap<TQString, MyMoneyBudget> budgets;
while (q.next()) {
MyMoneyDbTable::field_iterator ft = t.begin();
int i = 0;
TQDomDocument d;
while (ft != t.end()) {
CASE(XML) d.setContent(GETSTRING, false);
++ft; ++i;
}
TQDomNode child = d.firstChild();
child = child.firstChild();
MyMoneyBudget budget (child.toElement());
budgets.insert(budget.id(), budget);
signalProgress(++progress, 0);
}
return budgets;
}
void MyMoneyStorageSql::readBudgets(void) {
m_storage->loadBudgets(fetchBudgets());
}
const MyMoneyKeyValueContainer MyMoneyStorageSql::readKeyValuePairs (const TQString& kvpType, const TQString& kvpId) const {
DBG("*** Entering MyMoneyStorageSql::readKeyValuePairs");
MyMoneyKeyValueContainer list;
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
q.prepare ("SELECT kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type and kvpId = :id;");
q.bindValue(":type", kvpType);
q.bindValue(":id", kvpId);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Kvp for %1 %2").arg(kvpType)
.arg(kvpId)));
while (q.next()) list.setValue(q.value(0).toString(), q.value(1).toString());
return (list);
}
const TQMap<TQString, MyMoneyKeyValueContainer> MyMoneyStorageSql::readKeyValuePairs (const TQString& kvpType, const TQStringList& kvpIdList) const {
DBG("*** Entering MyMoneyStorageSql::readKeyValuePairs");
TQMap<TQString, MyMoneyKeyValueContainer> retval;
MyMoneySqlQuery q(const_cast <MyMoneyStorageSql*> (this));
TQString query ("SELECT kvpId, kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type");
if (!kvpIdList.empty()) {
query += " and kvpId IN ('" + kvpIdList.join("', '") + "')";
}
query += " order by kvpId;";
q.prepare (query);
q.bindValue(":type", kvpType);
if (!q.exec()) throw new MYMONEYEXCEPTION(buildError (q, __func__, TQString("reading Kvp List for %1").arg(kvpType)));
while (q.next()) {
retval [q.value(0).toString()].setValue(q.value(1).toString(), q.value(2).toString());
}
return (retval);
}
long unsigned MyMoneyStorageSql::getNextBudgetId() const {
const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
return m_hiIdBudgets;
}
long unsigned MyMoneyStorageSql::getNextAccountId() const {
const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
return m_hiIdAccounts;
}
long unsigned MyMoneyStorageSql::getNextInstitutionId() const {
const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
return m_hiIdInstitutions;
}
long unsigned MyMoneyStorageSql::getNextPayeeId() const {
const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
return m_hiIdPayees;
}
long unsigned MyMoneyStorageSql::getNextReportId() const {
const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
return m_hiIdReports;
}
long unsigned MyMoneyStorageSql::getNextScheduleId() const {
const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
return m_hiIdSchedules;
}
long unsigned MyMoneyStorageSql::getNextSecurityId() const {
const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
return m_hiIdSecurities;
}
long unsigned MyMoneyStorageSql::getNextTransactionId() const {
const_cast <MyMoneyStorageSql*> (this)->readFileInfo();
return m_hiIdTransactions;
}
long unsigned MyMoneyStorageSql::incrementBudgetId() {
MyMoneySqlQuery q(this);
startCommitUnit (__func__);
q.prepare("SELECT hiBudgetId FROM kmmFileInfo FOR UPDATE");
q.exec();
q.next();
long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
++returnValue;
q.prepare("UPDATE kmmFileInfo SET hiBudgetId = " + TQString::number(returnValue));
q.exec();
endCommitUnit (__func__);
m_hiIdBudgets = returnValue;
return returnValue;
}
long unsigned MyMoneyStorageSql::incrementAccountId() {
MyMoneySqlQuery q(this);
startCommitUnit (__func__);
q.prepare("SELECT hiAccountId FROM kmmFileInfo FOR UPDATE");
q.exec();
q.next();
long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
++returnValue;
q.prepare("UPDATE kmmFileInfo SET hiAccountId = " + TQString::number(returnValue));
q.exec();
endCommitUnit (__func__);
m_hiIdAccounts = returnValue;
return returnValue;
}
long unsigned MyMoneyStorageSql::incrementInstitutionId() {
MyMoneySqlQuery q(this);
startCommitUnit (__func__);
q.prepare("SELECT hiInstitutionId FROM kmmFileInfo FOR UPDATE");
q.exec();
q.next();
long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
++returnValue;
q.prepare("UPDATE kmmFileInfo SET hiInstitutionId = " + TQString::number(returnValue));
q.exec();
endCommitUnit (__func__);
m_hiIdInstitutions = returnValue;
return returnValue;
}
long unsigned MyMoneyStorageSql::incrementPayeeId() {
MyMoneySqlQuery q(this);
startCommitUnit (__func__);
q.prepare("SELECT hiPayeeId FROM kmmFileInfo FOR UPDATE");
q.exec();
q.next();
long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
++returnValue;
q.prepare("UPDATE kmmFileInfo SET hiPayeeId = " + TQString::number(returnValue));
q.exec();
endCommitUnit (__func__);
m_hiIdPayees = returnValue;
return returnValue;
}
long unsigned MyMoneyStorageSql::incrementReportId() {
MyMoneySqlQuery q(this);
startCommitUnit (__func__);
q.prepare("SELECT hiReportId FROM kmmFileInfo FOR UPDATE");
q.exec();
q.next();
long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
++returnValue;
q.prepare("UPDATE kmmFileInfo SET hiReportId = " + TQString::number(returnValue));
q.exec();
endCommitUnit (__func__);
m_hiIdReports = returnValue;
return returnValue;
}
long unsigned MyMoneyStorageSql::incrementScheduleId() {
MyMoneySqlQuery q(this);
startCommitUnit (__func__);
q.prepare("SELECT hiScheduleId FROM kmmFileInfo FOR UPDATE");
q.exec();
q.next();
long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
++returnValue;
q.prepare("UPDATE kmmFileInfo SET hiScheduleId = " + TQString::number(returnValue));
q.exec();
endCommitUnit (__func__);
m_hiIdSchedules = returnValue;
return returnValue;
}
long unsigned MyMoneyStorageSql::incrementSecurityId() {
MyMoneySqlQuery q(this);
startCommitUnit (__func__);
q.prepare("SELECT hiSecurityId FROM kmmFileInfo FOR UPDATE");
q.exec();
q.next();
long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
++returnValue;
q.prepare("UPDATE kmmFileInfo SET hiSecurityId = " + TQString::number(returnValue));
q.exec();
endCommitUnit (__func__);
m_hiIdSecurities = returnValue;
return returnValue;
}
long unsigned MyMoneyStorageSql::incrementTransactionId() {
MyMoneySqlQuery q(this);
startCommitUnit (__func__);
q.prepare("SELECT hiTransactionId FROM kmmFileInfo FOR UPDATE");
q.exec();
q.next();
long unsigned returnValue = (unsigned long) q.value(0).toULongLong();
++returnValue;
q.prepare("UPDATE kmmFileInfo SET hiTransactionId = " + TQString::number(returnValue));
q.exec();
endCommitUnit (__func__);
m_hiIdTransactions = returnValue;
return returnValue;
}
void MyMoneyStorageSql::loadAccountId(const unsigned long& id)
{
m_hiIdAccounts = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadTransactionId(const unsigned long& id)
{
m_hiIdTransactions = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadPayeeId(const unsigned long& id)
{
m_hiIdPayees = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadInstitutionId(const unsigned long& id)
{
m_hiIdInstitutions = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadScheduleId(const unsigned long& id)
{
m_hiIdSchedules = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadSecurityId(const unsigned long& id)
{
m_hiIdSecurities = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadReportId(const unsigned long& id)
{
m_hiIdReports = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadBudgetId(const unsigned long& id)
{
m_hiIdBudgets = id;
writeFileInfo();
}
//****************************************************
long unsigned MyMoneyStorageSql::calcHighId
(const long unsigned& i, const TQString& id) {
DBG("*** Entering MyMoneyStorageSql::calcHighId");
TQString nid = id;
long unsigned high = (unsigned long) nid.replace(TQRegExp("[A-Z]*"), "").toULongLong();
return std::max(high, i);
}
void MyMoneyStorageSql::setProgressCallback(void(*callback)(int, int, const TQString&)) {
m_progressCallback = callback;
}
void MyMoneyStorageSql::signalProgress(int current, int total, const TQString& msg) const {
if (m_progressCallback != 0)
(*m_progressCallback)(current, total, msg);
}
// **************************** Error display routine *******************************
TQString& MyMoneyStorageSql::buildError (const TQSqlQuery& q, const TQString& function, const TQString& message) const {
TQString s = TQString("Error in function %1 : %2").arg(function).arg(message);
TQSqlError e = lastError();
s += TQString ("\nDriver = %1, Host = %2, User = %3, Database = %4")
.arg(driverName()).arg(hostName()).arg(userName()).arg(databaseName());
s += TQString ("\nDriver Error: %1").arg(e.driverText());
s += TQString ("\nDatabase Error No %1: %2").arg(e.number()).arg(e.databaseText());
e = q.lastError();
s += TQString ("\nExecuted: %1").arg(q.executedQuery());
s += TQString ("\nQuery error No %1: %2").arg(e.number()).arg(e.text());
const_cast <MyMoneyStorageSql*> (this)->m_error = s;
tqDebug("%s", s.ascii());
const_cast <MyMoneyStorageSql*> (this)->cancelCommitUnit(function);
return (const_cast <MyMoneyStorageSql*> (this)->m_error);
}
// ************************* Build table descriptions ****************************
MyMoneyDbDef::MyMoneyDbDef () {
FileInfo();
Institutions();
Payees();
Accounts();
Transactions();
Splits();
KeyValuePairs();
Schedules();
SchedulePaymentHistory();
Securities();
Prices();
Currencies();
Reports();
Budgets();
Balances();
}
/* PRIMARYKEY - these fields combine to form a unique key field on which the db will create an index
NOTNULL - this field should never be null
UNSIGNED - for numeric types, indicates the field is UNSIGNED
?ISKEY - where there is no primary key, these fields can be used to uniquely identify a record
Default is that a field is not a part of a primary key, nullable, and if numeric, signed */
#define PRIMARYKEY true
#define NOTNULL true
#define UNSIGNED false
//#define ISKEY true
void MyMoneyDbDef::FileInfo(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("version", "varchar(16)"));
fields.append(new MyMoneyDbColumn("created", "date"));
fields.append(new MyMoneyDbColumn("lastModified", "date"));
fields.append(new MyMoneyDbColumn("baseCurrency", "char(3)"));
fields.append(new MyMoneyDbIntColumn("institutions", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("accounts", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("payees", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("transactions", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("splits", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("securities", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("prices", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("currencies", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("schedules", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("reports", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("kvps", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbColumn("dateRangeStart", "date"));
fields.append(new MyMoneyDbColumn("dateRangeEnd", "date"));
fields.append(new MyMoneyDbIntColumn("hiInstitutionId", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("hiPayeeId", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("hiAccountId", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("hiTransactionId", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("hiScheduleId", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("hiSecurityId", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("hiReportId", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbColumn("encryptData", "varchar(255)"));
fields.append(new MyMoneyDbColumn("updateInProgress", "char(1)"));
fields.append(new MyMoneyDbIntColumn("budgets", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("hiBudgetId", MyMoneyDbIntColumn::BIG, UNSIGNED));
fields.append(new MyMoneyDbColumn("logonUser", "varchar(255)"));
fields.append(new MyMoneyDbDatetimeColumn("logonAt"));
fields.append(new MyMoneyDbIntColumn("fixLevel",
MyMoneyDbIntColumn::MEDIUM, UNSIGNED));
MyMoneyDbTable t("kmmFileInfo", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Institutions(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbTextColumn("name", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("manager"));
fields.append(new MyMoneyDbTextColumn("routingCode"));
fields.append(new MyMoneyDbTextColumn("addressStreet"));
fields.append(new MyMoneyDbTextColumn("addressCity"));
fields.append(new MyMoneyDbTextColumn("addressZipcode"));
fields.append(new MyMoneyDbTextColumn("telephone"));
MyMoneyDbTable t("kmmInstitutions", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Payees(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbTextColumn("name"));
fields.append(new MyMoneyDbTextColumn("reference"));
fields.append(new MyMoneyDbTextColumn("email"));
fields.append(new MyMoneyDbTextColumn("addressStreet"));
fields.append(new MyMoneyDbTextColumn("addressCity"));
fields.append(new MyMoneyDbTextColumn("addressZipcode"));
fields.append(new MyMoneyDbTextColumn("addressState"));
fields.append(new MyMoneyDbTextColumn("telephone"));
fields.append(new MyMoneyDbTextColumn("notes", MyMoneyDbTextColumn::LONG));
fields.append(new MyMoneyDbColumn("defaultAccountId", "varchar(32)"));
fields.append(new MyMoneyDbIntColumn("matchData", MyMoneyDbIntColumn::TINY, UNSIGNED));
fields.append(new MyMoneyDbColumn("matchIgnoreCase", "char(1)"));
fields.append(new MyMoneyDbTextColumn("matchKeys"));
MyMoneyDbTable t("kmmPayees", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Accounts(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbColumn("institutionId", "varchar(32)"));
fields.append(new MyMoneyDbColumn("parentId", "varchar(32)"));
fields.append(new MyMoneyDbDatetimeColumn("lastReconciled"));
fields.append(new MyMoneyDbDatetimeColumn("lastModified"));
fields.append(new MyMoneyDbColumn("openingDate", "date"));
fields.append(new MyMoneyDbTextColumn("accountNumber"));
fields.append(new MyMoneyDbColumn("accountType", "varchar(16)", false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("accountTypeString"));
fields.append(new MyMoneyDbColumn("isStockAccount", "char(1)"));
fields.append(new MyMoneyDbTextColumn("accountName"));
fields.append(new MyMoneyDbTextColumn("description"));
fields.append(new MyMoneyDbColumn("currencyId", "varchar(32)"));
fields.append(new MyMoneyDbTextColumn("balance"));
fields.append(new MyMoneyDbTextColumn("balanceFormatted"));
fields.append(new MyMoneyDbIntColumn("transactionCount", MyMoneyDbIntColumn::BIG, UNSIGNED));
MyMoneyDbTable t("kmmAccounts", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Transactions(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbColumn("txType", "char(1)"));
fields.append(new MyMoneyDbDatetimeColumn("postDate"));
fields.append(new MyMoneyDbTextColumn("memo"));
fields.append(new MyMoneyDbDatetimeColumn("entryDate"));
fields.append(new MyMoneyDbColumn("currencyId", "char(3)"));
fields.append(new MyMoneyDbTextColumn("bankId"));
MyMoneyDbTable t("kmmTransactions", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Splits(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("transactionId", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbColumn("txType", "char(1)"));
fields.append(new MyMoneyDbIntColumn("splitId", MyMoneyDbIntColumn::SMALL, UNSIGNED, PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbColumn("payeeId", "varchar(32)"));
fields.append(new MyMoneyDbDatetimeColumn("reconcileDate"));
fields.append(new MyMoneyDbColumn("action", "varchar(16)"));
fields.append(new MyMoneyDbColumn("reconcileFlag", "char(1)"));
fields.append(new MyMoneyDbTextColumn("value", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
fields.append(new MyMoneyDbColumn("valueFormatted", "text"));
fields.append(new MyMoneyDbTextColumn("shares", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("sharesFormatted"));
fields.append(new MyMoneyDbTextColumn("price", MyMoneyDbTextColumn::NORMAL, false));
fields.append(new MyMoneyDbTextColumn("priceFormatted"));
fields.append(new MyMoneyDbTextColumn("memo"));
fields.append(new MyMoneyDbColumn("accountId", "varchar(32)", false, NOTNULL));
fields.append(new MyMoneyDbColumn("checkNumber", "varchar(32)"));
fields.append(new MyMoneyDbDatetimeColumn("postDate"));
fields.append(new MyMoneyDbTextColumn("bankId"));
MyMoneyDbTable t("kmmSplits", fields);
TQStringList list;
list << "accountId" << "txType";
t.addIndex("kmmSplitsaccount_type", list, false);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::KeyValuePairs(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("kvpType", "varchar(16)", false, NOTNULL));
fields.append(new MyMoneyDbColumn("kvpId", "varchar(32)"));
fields.append(new MyMoneyDbColumn("kvpKey", "varchar(255)", false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("kvpData"));
MyMoneyDbTable t("kmmKeyValuePairs", fields);
TQStringList list;
list << "kvpType" << "kvpId";
t.addIndex("type_id", list, false);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Schedules(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbTextColumn("name", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
fields.append(new MyMoneyDbIntColumn("type", MyMoneyDbIntColumn::TINY, UNSIGNED, false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("typeString"));
fields.append(new MyMoneyDbIntColumn("occurence", MyMoneyDbIntColumn::SMALL, UNSIGNED, false,
NOTNULL));
fields.append(new MyMoneyDbIntColumn("occurenceMultiplier", MyMoneyDbIntColumn::SMALL, UNSIGNED,
false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("occurenceString"));
fields.append(new MyMoneyDbIntColumn("paymentType", MyMoneyDbIntColumn::TINY, UNSIGNED));
fields.append(new MyMoneyDbTextColumn("paymentTypeString", MyMoneyDbTextColumn::LONG));
fields.append(new MyMoneyDbColumn("startDate", "date", false, NOTNULL));
fields.append(new MyMoneyDbColumn("endDate", "date"));
fields.append(new MyMoneyDbColumn("fixed", "char(1)", false, NOTNULL));
fields.append(new MyMoneyDbColumn("autoEnter", "char(1)", false, NOTNULL));
fields.append(new MyMoneyDbColumn("lastPayment", "date"));
fields.append(new MyMoneyDbColumn("nextPaymentDue", "date"));
fields.append(new MyMoneyDbIntColumn("weekendOption", MyMoneyDbIntColumn::TINY, UNSIGNED, false,
NOTNULL));
fields.append(new MyMoneyDbTextColumn("weekendOptionString"));
MyMoneyDbTable t("kmmSchedules", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::SchedulePaymentHistory(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("schedId", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbColumn("payDate", "date", PRIMARYKEY, NOTNULL));
MyMoneyDbTable t("kmmSchedulePaymentHistory", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Securities(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbColumn("name", "text", false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("symbol"));
fields.append(new MyMoneyDbIntColumn("type", MyMoneyDbIntColumn::SMALL, UNSIGNED, false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("typeString"));
fields.append(new MyMoneyDbColumn("smallestAccountFraction", "varchar(24)"));
fields.append(new MyMoneyDbTextColumn("tradingMarket"));
fields.append(new MyMoneyDbColumn("tradingCurrency", "char(3)"));
MyMoneyDbTable t("kmmSecurities", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Prices(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("fromId", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbColumn("toId", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbColumn("priceDate", "date", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbTextColumn("price", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("priceFormatted"));
fields.append(new MyMoneyDbTextColumn("priceSource"));
MyMoneyDbTable t("kmmPrices", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Currencies(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("ISOcode", "char(3)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbTextColumn("name", MyMoneyDbTextColumn::NORMAL, false, NOTNULL));
fields.append(new MyMoneyDbIntColumn("type", MyMoneyDbIntColumn::SMALL, UNSIGNED));
fields.append(new MyMoneyDbTextColumn("typeString"));
fields.append(new MyMoneyDbIntColumn("symbol1", MyMoneyDbIntColumn::SMALL, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("symbol2", MyMoneyDbIntColumn::SMALL, UNSIGNED));
fields.append(new MyMoneyDbIntColumn("symbol3", MyMoneyDbIntColumn::SMALL, UNSIGNED));
fields.append(new MyMoneyDbColumn("symbolString", "varchar(255)"));
fields.append(new MyMoneyDbColumn("partsPerUnit", "varchar(24)"));
fields.append(new MyMoneyDbColumn("smallestCashFraction", "varchar(24)"));
fields.append(new MyMoneyDbColumn("smallestAccountFraction", "varchar(24)"));
MyMoneyDbTable t("kmmCurrencies", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Reports(void) {
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("name", "varchar(255)", false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("XML", MyMoneyDbTextColumn::LONG));
fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
MyMoneyDbTable t("kmmReportConfig", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Budgets(void){
TQValueList<KSharedPtr <MyMoneyDbColumn> > fields;
fields.append(new MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL));
fields.append(new MyMoneyDbColumn("name", "text", false, NOTNULL));
fields.append(new MyMoneyDbColumn("start", "date", false, NOTNULL));
fields.append(new MyMoneyDbTextColumn("XML", MyMoneyDbTextColumn::LONG));
MyMoneyDbTable t("kmmBudgetConfig", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Balances(void){
MyMoneyDbView v("kmmBalances", "CREATE VIEW kmmBalances AS "
"SELECT kmmAccounts.id AS id, kmmAccounts.currencyId, "
"kmmSplits.txType, kmmSplits.value, kmmSplits.shares, "
"kmmSplits.postDate AS balDate, "
"kmmTransactions.currencyId AS txCurrencyId "
"FROM kmmAccounts, kmmSplits, kmmTransactions "
"WHERE kmmSplits.txType = 'N' "
"AND kmmSplits.accountId = kmmAccounts.id "
"AND kmmSplits.transactionId = kmmTransactions.id;");
m_views[v.name()] = v;
}
// function to write create SQL to a stream
const TQString MyMoneyDbDef::generateSQL (const TQString& driver) const {
TQString retval;
databaseTypeE dbType = m_drivers.driverToType(driver);
table_iterator tt = tableBegin();
while (tt != tableEnd()) {
retval += (*tt).generateCreateSQL(dbType) + '\n';
++tt;
}
view_iterator vt = viewBegin();
while (vt != viewEnd()) {
retval += (*vt).createString() + '\n';
++vt;
}
retval += '\n';
MyMoneyDbTable fi = m_tables["kmmFileInfo"];
TQString qs = fi.insertString();
MyMoneyDbTable::field_iterator fit;
for (fit = fi.begin(); fit != fi.end(); ++fit) {
TQString toReplace = (*fit)->name();
toReplace.prepend(':');
TQString replace = "NULL";
if ((*fit)->name() == "version")
replace = TQString::number(m_currentVersion);
if ((*fit)->name() == "fixLevel")
replace = TQString::number
(MyMoneyFile::instance()->storage()->currentFixVersion());
if ((*fit)->name() == "created")
replace = TQDate::currentDate().toString(Qt::ISODate);
if ((*fit)->name() == "lastModified")
replace = TQDate::currentDate().toString(Qt::ISODate);
if ((*fit)->name() == "updateInProgress")
replace = enclose("N");
qs.replace(toReplace, replace);
}
qs += "\n\n";
retval += qs;
qs = TQString();
unsigned int i;
TQValueList<MyMoneyAccount> stdList;
stdList.append (MyMoneyFile::instance()->asset());
stdList.append (MyMoneyFile::instance()->equity());
stdList.append (MyMoneyFile::instance()->expense());
stdList.append (MyMoneyFile::instance()->income());
stdList.append (MyMoneyFile::instance()->liability());
for (i = 0; i < stdList.count(); ++i) {
MyMoneyAccount* pac = &stdList[i];
MyMoneyDbTable ac = m_tables["kmmAccounts"];
qs = ac.insertString();
MyMoneyDbTable::field_iterator act;
// do the following in reverse so the 'formatted' fields are
// correctly handled.
// Hmm, how does one use a TQValueListIterator in reverse
// It'll be okay in TQt4 with TQListIterator
for (act = ac.end(), --act; act != ac.begin(); --act) {
TQString toReplace = (*act)->name();
toReplace.prepend(':');
TQString replace = "NULL";
if ((*act)->name() == "accountType")
replace = TQString::number(pac->accountType());
if ((*act)->name() == "accountTypeString")
replace = enclose(pac->name());
if ((*act)->name() == "isStockAccount")
replace = enclose("N");
if ((*act)->name() == "accountName")
replace = enclose(pac->name());
qs.replace(toReplace, replace);
}
qs.replace (":id", enclose(pac->id())); // a real kludge
qs += "\n\n";
retval += qs;
}
return retval;
}
//*****************************************************************************
void MyMoneyDbTable::addIndex(const TQString& name, const TQStringList& columns, bool unique) {
m_indices.push_back (MyMoneyDbIndex (m_name, name, columns, unique));
}
void MyMoneyDbTable::buildSQLStrings (void) {
// build fixed SQL strings for this table
// build the insert string with placeholders for each field
TQString qs = TQString("INSERT INTO %1 (").arg(name());
TQString ws = ") VALUES (";
field_iterator ft = m_fields.begin();
while (ft != m_fields.end()) {
qs += TQString("%1, ").arg((*ft)->name());
ws += TQString(":%1, ").arg((*ft)->name());
++ft;
}
qs = qs.left(qs.length() - 2);
ws = ws.left(ws.length() - 2);
m_insertString = qs + ws + ");";
// build a 'select all' string (select * is deprecated)
// don't terminate with semicolon coz we may want a where or order clause
m_selectAllString = "SELECT " + columnList() + " FROM " + name();;
// build an update string; key fields go in the where clause
qs = "UPDATE " + name() + " SET ";
ws = TQString();
ft = m_fields.begin();
while (ft != m_fields.end()) {
if ((*ft)->isPrimaryKey()) {
if (!ws.isEmpty()) ws += " AND ";
ws += TQString("%1 = :%2").arg((*ft)->name()).arg((*ft)->name());
} else {
qs += TQString("%1 = :%2, ").arg((*ft)->name()).arg((*ft)->name());
}
++ft;
}
qs = qs.left(qs.length() - 2);
if (!ws.isEmpty()) qs += " WHERE " + ws;
m_updateString = qs + ";";
// build a delete string; where clause as for update
qs = "DELETE FROM " + name();
if (!ws.isEmpty()) qs += " WHERE " + ws;
m_deleteString = qs + ";";
}
const TQString MyMoneyDbTable::columnList() const {
field_iterator ft = m_fields.begin();
TQString qs;
ft = m_fields.begin();
while (ft != m_fields.end()) {
qs += TQString("%1, ").arg((*ft)->name());
++ft;
}
return (qs.left(qs.length() - 2));
}
const TQString MyMoneyDbTable::generateCreateSQL (databaseTypeE dbType) const {
TQString qs = TQString("CREATE TABLE %1 (").arg(name());
TQString pkey;
for (field_iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
qs += (*it)->generateDDL (dbType) + ", ";
if ((*it)->isPrimaryKey ())
pkey += (*it)->name () + ", ";
}
if (!pkey.isEmpty()) {
qs += "PRIMARY KEY (" + pkey;
qs = qs.left(qs.length() -2) + "))";
} else {
qs = qs.left(qs.length() -2) + ")";
}
if (dbType == Mysql)
qs += " ENGINE = InnoDB;\n";
else
qs += ";\n";
for (index_iterator ii = m_indices.begin(); ii != m_indices.end(); ++ii) {
qs += (*ii).generateDDL(dbType);
}
return qs;
}
const TQString MyMoneyDbTable::dropPrimaryKeyString(databaseTypeE dbType) const
{
if (dbType == Mysql || dbType == Oracle8)
return "ALTER TABLE " + m_name + " DROP PRIMARY KEY;";
else if (dbType == Postgresql)
return "ALTER TABLE " + m_name + " DROP CONSTRAINT " + m_name + "_pkey;";
else if (dbType == Sqlite3)
return "";
return "";
}
const TQString MyMoneyDbTable::modifyColumnString(databaseTypeE dbType, const TQString& columnName, const MyMoneyDbColumn& newDef) const {
TQString qs = "ALTER TABLE " + m_name + " ";
if (dbType == Mysql)
qs += "CHANGE " + columnName + " " + newDef.generateDDL(dbType);
else if (dbType == Postgresql)
qs += "ALTER COLUMN " + columnName + " TYPE " + newDef.generateDDL(dbType).section(' ', 1);
else if (dbType == Sqlite3)
qs = "";
else if (dbType == Oracle8)
qs = "MODIFY " + columnName + " " + newDef.generateDDL(dbType);
return qs;
}
//*****************************************************************************
const TQString MyMoneyDbIndex::generateDDL (databaseTypeE dbType) const
{
Q_UNUSED(dbType);
TQString qs = "CREATE ";
if (m_unique)
qs += "UNITQUE ";
qs += "INDEX " + m_table + "_" + m_name + "_idx ON "
+ m_table + " (";
// The following should probably be revised. MySQL supports an index on
// partial columns, but not on a function. Postgres supports an index on
// the result of an SQL function, but not a partial column. There should be
// a way to merge these, and support other DBMSs like SQLite at the same time.
// For now, if we just use plain columns, this will work fine.
for (TQStringList::const_iterator it = m_columns.begin(); it != m_columns.end(); ++it) {
qs += *it + ",";
}
qs = qs.left(qs.length() - 1) + ");\n";
return qs;
}
//*****************************************************************************
// These are the actual column types.
// TODO: consider changing all the else-if statements to driver classes.
//
MyMoneyDbColumn* MyMoneyDbColumn::clone () const
{ return (new MyMoneyDbColumn (*this)); }
MyMoneyDbIntColumn* MyMoneyDbIntColumn::clone () const
{ return (new MyMoneyDbIntColumn (*this)); }
MyMoneyDbDatetimeColumn* MyMoneyDbDatetimeColumn::clone () const
{ return (new MyMoneyDbDatetimeColumn (*this)); }
MyMoneyDbTextColumn* MyMoneyDbTextColumn::clone () const
{ return (new MyMoneyDbTextColumn (*this)); }
const TQString MyMoneyDbColumn::generateDDL (databaseTypeE dbType) const
{
Q_UNUSED(dbType);
TQString qs = name() + " " + type();
if (isNotNull()) qs += " NOT NULL";
return qs;
}
const TQString MyMoneyDbIntColumn::generateDDL (databaseTypeE dbType) const
{
TQString qs = name() + " ";
switch (m_type) {
case MyMoneyDbIntColumn::TINY:
if (dbType == Mysql || dbType == Sqlite3) {
qs += "tinyint ";
} else if (dbType == Postgresql) {
qs += "int2 ";
} else if (dbType == Db2) {
qs += "smallint ";
} else if (dbType == Oracle8) {
qs += "number(3) ";
} else {
// cross your fingers...
qs += "smallint ";
}
break;
case MyMoneyDbIntColumn::SMALL:
if (dbType == Mysql || dbType == Db2 || dbType == Sqlite3) {
qs += "smallint ";
} else if (dbType == Postgresql) {
qs += "int2 ";
} else if (dbType == Oracle8) {
qs += "number(5) ";
} else {
// cross your fingers...
qs += "smallint ";
}
break;
case MyMoneyDbIntColumn::MEDIUM:
if (dbType == Mysql || dbType == Db2) {
qs += "int ";
} else if (dbType == Postgresql) {
qs += "int4 ";
} else if (dbType == Sqlite3) {
qs += "integer ";
} else if (dbType == Oracle8) {
qs += "number(10) ";
} else {
// cross your fingers...
qs += "int ";
}
break;
case MyMoneyDbIntColumn::BIG:
if (dbType == Mysql || dbType == Db2 || dbType == Sqlite3) {
qs += "bigint ";
} else if (dbType == Postgresql) {
qs += "int8 ";
} else if (dbType == Oracle8) {
qs += "number(20) ";
} else {
// cross your fingers...
qs += "bigint ";
}
break;
default:
qs += "int ";
break;
}
if ((! m_isSigned) && (dbType == Mysql || dbType == Sqlite3)) {
qs += "unsigned ";
}
if (isNotNull()) qs += " NOT NULL";
if ((! m_isSigned) && (dbType == Postgresql)) {
qs += " check(" + name() + " >= 0)";
}
return qs;
}
const TQString MyMoneyDbTextColumn::generateDDL (databaseTypeE dbType) const
{
TQString qs = name() + " ";
switch (m_type) {
case MyMoneyDbTextColumn::TINY:
if (dbType == Mysql || dbType == Sqlite3) {
qs += "tinytext ";
} else if (dbType == Postgresql) {
qs += "text ";
} else if (dbType == Db2) {
qs += "varchar(255) ";
} else if (dbType == Oracle8) {
qs += "varchar2(255) ";
} else {
// cross your fingers...
qs += "tinytext ";
}
break;
case MyMoneyDbTextColumn::NORMAL:
if (dbType == Mysql || dbType == Sqlite3 || dbType == Postgresql) {
qs += "text ";
} else if (dbType == Db2) {
qs += "clob(64K) ";
} else if (dbType == Oracle8) {
qs += "clob ";
} else {
// cross your fingers...
qs += "text ";
}
break;
case MyMoneyDbTextColumn::MEDIUM:
if (dbType == Mysql || dbType == Sqlite3 ) {
qs += "mediumtext ";
} else if (dbType == Postgresql) {
qs += "text ";
} else if (dbType == Db2) {
qs += "clob(16M) ";
} else if (dbType == Oracle8) {
qs += "clob ";
} else {
// cross your fingers...
qs += "mediumtext ";
}
break;
case MyMoneyDbTextColumn::LONG:
if (dbType == Mysql || dbType == Sqlite3 ) {
qs += "longtext ";
} else if (dbType == Postgresql) {
qs += "text ";
} else if (dbType == Db2) {
qs += "clob(2G) ";
} else if (dbType == Oracle8) {
qs += "clob ";
} else {
// cross your fingers...
qs += "longtext ";
}
break;
default:
if (dbType == Oracle8) {
qs += "clob ";
} else {
qs += "text ";
}
break;
}
if (isNotNull()) qs += " NOT NULL";
return qs;
}
const TQString MyMoneyDbDatetimeColumn::generateDDL (databaseTypeE dbType) const
{
TQString qs = name() + " ";
if (dbType == Mysql || dbType == ODBC3) {
qs += "datetime ";
} else if (dbType == Postgresql || dbType == Db2 || dbType == Oracle8 || dbType == Sqlite3 ) {
qs += "timestamp ";
} else {
qs += "";
}
if (isNotNull()) qs += " NOT NULL";
return qs;
}