From 85e04f4305a4b15ae3575f512d54341d7de9e2ed Mon Sep 17 00:00:00 2001 From: Sergey Yanovich Date: Mon, 14 Apr 2008 21:40:05 +0300 Subject: [PATCH] [storage] Allow transfer rollbacks (bug 155) --- storage/account/test/aaCurrencyTest.cpp | 127 +++++++++++++++++++++++++++- storage/account/test/aaCurrencyTest.h | 3 +- storage/base/aaSaveEvent.cpp | 145 ++++++++++++++++++++++++++++++-- storage/base/aaSaveEvent.h | 7 +- storage/base/aaStateRow.cpp | 17 +++- storage/base/aaStateRow.h | 2 +- 6 files changed, 288 insertions(+), 13 deletions(-) diff --git a/storage/account/test/aaCurrencyTest.cpp b/storage/account/test/aaCurrencyTest.cpp index 81ff762..d27d046 100644 --- a/storage/account/test/aaCurrencyTest.cpp +++ b/storage/account/test/aaCurrencyTest.cpp @@ -68,6 +68,7 @@ aaCurrencyTest::Test(nsITestRunner *aTestRunner) testForexSale(aTestRunner); testRateChange(aTestRunner); testForexSaleAfterChange(aTestRunner); + testTransferRollback(aTestRunner); return NS_OK; } @@ -100,6 +101,7 @@ aaCurrencyTest::RAII::~RAII() { test->mFlows = nsnull; test->mResources = nsnull; + test->mEntities = nsnull; test->mBank = nsnull; test->mSession = nsnull; test = nsnull; @@ -951,7 +953,130 @@ aaCurrencyTest::testForexSaleAfterChange(nsITestRunner *aTestRunner) } nsresult -aaCurrencyTest::testLoadQuotes(nsITestRunner *aTestRunner) +aaCurrencyTest::testTransferRollback(nsITestRunner *aTestRunner) { + nsresult rv; + NS_TEST_BEGIN(aTestRunner); + + rv = mSession->CreateQuery(AA_LOADENTITY_CONTRACT_ID, + getter_AddRefs(mEntities)); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mEntities->Load(); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr c3, y; + y = do_QueryElementAt(mResources, 4); + + nsCOMPtr S2; + S2 = do_QueryElementAt(mEntities, 2); + + nsCOMPtr dy, by3; + dy = do_QueryElementAt(mFlows, 4); + + nsCOMPtr quote; + nsCOMPtr fact; + nsCOMPtr event; + nsCOMPtr block; + nsCOMPtr txn; + PRExplodedTime tm = {0,0,0,12,14,3,2008}; + + /* */ + { + c3 = do_CreateInstance("@aasii.org/base/resource;1", &rv); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = c3->SetTag(NS_LITERAL_STRING("c3")); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = mSession->Save(c3, nsnull); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + + by3 = do_CreateInstance("@aasii.org/base/flow;1", &rv); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + by3->SetEntity(S2); + by3->SetGiveResource(c3); + by3->SetTakeResource(y); + by3->SetRate(1.0/200.0); + by3->SetTag(NS_LITERAL_STRING("by3")); + rv = mSession->Save(by3, nsnull); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + + quote = do_CreateInstance("@aasii.org/base/quote;1", &rv); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + quote->SetResource(c3); + quote->SetRate(0.8); + quote->SetTime(PR_ImplodeTime(&tm)); + rv = mSession->Save(quote, nsnull); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + { + fact = do_CreateInstance("@aasii.org/base/fact;1", &rv); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + fact->SetTakeFrom(by3); + fact->SetGiveTo(dy); + fact->SetAmount(100.0); + event = do_CreateInstance("@aasii.org/base/event;1", &rv); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + tm.tm_mday = 11; + event->SetTime(PR_ImplodeTime(&tm)); + block = do_QueryInterface(event, &rv); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + block->AppendElement(fact, PR_FALSE); + rv = mSession->Save(event, nsnull); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr set, factSet; + mSession->CreateQuery(AA_LOADFLOWSTATES_CONTRACT_ID, getter_AddRefs(set)); + NS_TEST_ASSERT_MSG(set, "[rollback] creating loader" ); + NS_ENSURE_SUCCESS(rv, rv); + + rv = set->Load(); + NS_TEST_ASSERT_MSG(NS_SUCCEEDED(rv), "[rollback] loading states" ); + + PRUint32 count; + set->GetLength(&count); + NS_TEST_ASSERT_MSG(count == 10, "[rollback] wrong flow count"); + + rv = mSession->CreateQuery(AA_LOADFACTLIST_CONTRACT_ID, + getter_AddRefs( factSet )); + NS_TEST_ASSERT_MSG(factSet, "[rollback] query instance creation" ); + NS_ENSURE_TRUE(factSet, rv); + + rv = factSet->Load(); + NS_TEST_ASSERT_MSG(NS_SUCCEEDED(rv), "[rollback] loading facts" ); + + factSet->GetLength(&count); + NS_TEST_ASSERT_MSG(count == 7, "[rollback] wrong fact count"); + + rv = mSession->Save(event, event); + NS_TEST_ASSERT_OK(rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = set->Load(); + NS_TEST_ASSERT_MSG(NS_SUCCEEDED(rv), "[rollback] loading states" ); + + set->GetLength(&count); + NS_TEST_ASSERT_MSG(count == 8, "[rollback] wrong flow count"); + + rv = factSet->Load(); + NS_TEST_ASSERT_MSG(NS_SUCCEEDED(rv), "[rollback] loading facts" ); + + factSet->GetLength(&count); + NS_TEST_ASSERT_MSG(count == 6, "[rollback] wrong fact count"); + } + return NS_OK; } diff --git a/storage/account/test/aaCurrencyTest.h b/storage/account/test/aaCurrencyTest.h index f0a7799..8130141 100644 --- a/storage/account/test/aaCurrencyTest.h +++ b/storage/account/test/aaCurrencyTest.h @@ -56,6 +56,7 @@ private: nsCOMPtr mSession; nsCOMPtr mFlows; nsCOMPtr mResources; + nsCOMPtr mEntities; nsCOMPtr mBank; nsresult testPurchase(nsITestRunner *aTestRunner); @@ -63,7 +64,7 @@ private: nsresult testForexSale(nsITestRunner *aTestRunner); nsresult testRateChange(nsITestRunner *aTestRunner); nsresult testForexSaleAfterChange(nsITestRunner *aTestRunner); - nsresult testLoadQuotes(nsITestRunner *aTestRunner); + nsresult testTransferRollback(nsITestRunner *aTestRunner); }; #endif /* AACURRENCYTEST_H */ diff --git a/storage/base/aaSaveEvent.cpp b/storage/base/aaSaveEvent.cpp index d52819c..f72edb3 100644 --- a/storage/base/aaSaveEvent.cpp +++ b/storage/base/aaSaveEvent.cpp @@ -66,6 +66,17 @@ LEFT JOIN flow ON fact.flow_id == flow.id\ LEFT JOIN state ON fact.flow_id == state.flow_id AND state.paid IS NULL\ ORDER BY fact.flow_id, fact.side" +#define AA_CHECK_EVENT "SELECT COUNT(txn.value) FROM transfer\ + LEFT JOIN txn ON transfer.day == txn.day AND transfer.event_id == txn.event_id\ + AND transfer.id == txn.transfer_id\ + WHERE transfer.event_id = ? AND\ + transfer.day = round(julianday(?,'unixepoch'))" +#define AA_EVENT_CLEAN_FACTS "DELETE FROM fact WHERE event_id = ? AND\ + day = round(julianday(?,'unixepoch'))" +#define AA_EVENT_CLEAN_TRANSFERS "DELETE FROM transfer WHERE event_id = ? AND\ + day = round(julianday(?,'unixepoch'))" +#define AA_EVENT_CLEAN_EVENT "DELETE FROM fact WHERE event_id = ? AND\ + day = round(julianday(?,'unixepoch'))" /*************** aaSaveEvent **********************/ aaSaveEvent::aaSaveEvent(nsISupports *aOuter) @@ -90,15 +101,19 @@ NS_IMPL_ISUPPORTS1(aaSaveEvent, NS_IMETHODIMP aaSaveEvent::Save(aaIDataNode *aNode, aaIDataNode *aOldNode) { - NS_ENSURE_TRUE(! aOldNode, NS_ERROR_INVALID_ARG); - nsCOMPtr event(do_QueryInterface(aNode)); NS_ENSURE_ARG_POINTER(event); + nsresult rv; + if (aOldNode) { + rv = deleteEvent(aOldNode); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + nsCOMPtr block(do_QueryInterface(aNode)); NS_ENSURE_ARG_POINTER(block); - nsresult rv; PRUint32 i = 0, count; rv = block->GetLength( &count ); NS_ENSURE_SUCCESS(rv, rv); @@ -348,7 +363,7 @@ aaSaveEvent::saveFact(aaIFact *fact) } nsresult -aaSaveEvent::adjustStates(aaIEvent *aEvent) +aaSaveEvent::adjustStates(aaIEvent *aEvent, PRBool isRemoval) { nsresult rv; nsCOMPtr statement; @@ -382,7 +397,7 @@ aaSaveEvent::adjustStates(aaIEvent *aEvent) if (NS_FAILED(rv)) break; if (state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_EXECUTING) { - aaStateRow state(statement); + aaStateRow state(statement, isRemoval); if (! state.Adjust(mConnection) ) { rv = NS_ERROR_FAILURE; break; @@ -394,3 +409,123 @@ aaSaveEvent::adjustStates(aaIEvent *aEvent) return rv; } + +nsresult +aaSaveEvent::checkTransactions(aaIEvent *aEvent) +{ + nsresult rv; + nsCOMPtr statement; + + PRInt64 eventId; + rv = aEvent->GetId(&eventId); + NS_ENSURE_SUCCESS(rv, rv); + + PRTime time; + rv = aEvent->GetTime(&time); + NS_ENSURE_SUCCESS(rv, rv); + time /= 1000000; + + rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(AA_CHECK_EVENT), + getter_AddRefs(statement)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->BindInt64Parameter(1, time); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->BindInt64Parameter(0, eventId); + NS_ENSURE_SUCCESS(rv, rv); + + PRBool hasMore; + PRInt32 state; + do { + rv = statement->ExecuteStep(&hasMore); + if (NS_FAILED(rv)) + break; + rv = statement->GetState(&state); + if (NS_FAILED(rv)) + break; + if (state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_EXECUTING) { + PRInt32 count; + rv = statement->GetInt32(0, &count); + if (NS_FAILED(rv)) + break; + if (count != 0) { + rv = NS_ERROR_FAILURE; + break; + } + } + } while (hasMore); + statement->Reset(); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +nsresult +aaSaveEvent::cleanEvent(aaIEvent *aEvent) +{ + nsresult rv; + nsCOMPtr statement; + + PRInt64 eventId; + rv = aEvent->GetId(&eventId); + NS_ENSURE_SUCCESS(rv, rv); + + PRTime time; + rv = aEvent->GetTime(&time); + NS_ENSURE_SUCCESS(rv, rv); + time /= 1000000; + + static const char* queries[] = + { + AA_EVENT_CLEAN_FACTS + ,AA_EVENT_CLEAN_TRANSFERS + ,AA_EVENT_CLEAN_EVENT + }; + + PRUint32 i; + for (i = 0; i < 3; i++) { + rv = mConnection->CreateStatement(nsDependentCString(queries[i]), + getter_AddRefs(statement)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->BindInt64Parameter(1, time); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->BindInt64Parameter(0, eventId); + NS_ENSURE_SUCCESS(rv, rv); + + rv = statement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +aaSaveEvent::deleteEvent(aaIDataNode *aNode) +{ + nsCOMPtr event; + event = do_QueryInterface(aNode); + NS_ENSURE_ARG_POINTER(event); + + nsresult rv; + rv = mConnection->BeginTransaction(); + NS_ENSURE_SUCCESS(rv, rv); + + { + aaGuard guard(mConnection); + + rv = checkTransactions(event); + NS_ENSURE_SUCCESS(rv, rv); + + rv = adjustStates(event, true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = cleanEvent(event); + NS_ENSURE_SUCCESS(rv, rv); + + return guard.Dismiss(); + } + return NS_OK; +} diff --git a/storage/base/aaSaveEvent.h b/storage/base/aaSaveEvent.h index a377046..c0d69ca 100644 --- a/storage/base/aaSaveEvent.h +++ b/storage/base/aaSaveEvent.h @@ -1,7 +1,7 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent tw=79 ft=cpp: */ /* - * Copyright (C) 2007 Sergey Yanovich + * Copyright (C) 2007,2008 Sergey Yanovich * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -53,7 +53,10 @@ private: nsresult validateTransfer(PRInt64 fromFlowId, PRInt64 toFlowId); nsresult saveAct(aaIFact *fact); nsresult saveFact(aaIFact *fact); - nsresult adjustStates(aaIEvent *aEvent); + nsresult adjustStates(aaIEvent *aEvent, PRBool isRemoval = PR_FALSE); + nsresult checkTransactions(aaIEvent *aEvent); + nsresult cleanEvent(aaIEvent *aEvent); + nsresult deleteEvent(aaIDataNode *aNode); }; #endif /* AASAVEEVENT_H */ diff --git a/storage/base/aaStateRow.cpp b/storage/base/aaStateRow.cpp index 6baa534..c0069f5 100644 --- a/storage/base/aaStateRow.cpp +++ b/storage/base/aaStateRow.cpp @@ -38,7 +38,8 @@ #define AA_STATE_UPDATE "UPDATE state SET side = ?, amount = ? WHERE id == ?" #define AA_STATE_TERMINATE "UPDATE state SET paid = ? WHERE id == ?" -aaStateRow::aaStateRow(mozIStorageStatement *aStatement) +aaStateRow::aaStateRow(mozIStorageStatement *aStatement, + PRBool isRemoval = PR_FALSE) :mReady(PR_FALSE), mNew(PR_FALSE), mUpdate(PR_FALSE) { nsresult rv; @@ -56,6 +57,8 @@ aaStateRow::aaStateRow(mozIStorageStatement *aStatement) rv = aStatement->GetDouble(3, &mFactSum); if (NS_FAILED(rv)) return; + if (isRemoval) + mFactSum = -mFactSum; rv = aStatement->GetDouble(4, &mRate); if (NS_FAILED(rv)) return; @@ -66,6 +69,10 @@ aaStateRow::aaStateRow(mozIStorageStatement *aStatement) if (type == mozIStorageValueArray::VALUE_TYPE_NULL) { mSide = ! mFactSide; mSum = mFactSum * (mSide ? mRate : (1 / mRate)); + if (mSum < 0.0) { + mSide = ! mSide; + mSum *= (mSide ? mRate : (1 / mRate)); + } mNew = PR_TRUE; mReady = PR_TRUE; @@ -92,14 +99,18 @@ aaStateRow::aaStateRow(mozIStorageStatement *aStatement) if (mSide == mFactSide) { double delta = mSum - mFactSum; - if (delta > 0) { + if (delta > 0.0) { mSum = delta; } else { mSide = ! mSide; mSum = -delta * (mSide ? mRate : (1 / mRate)); } } else { - mSum += mFactSum; + mSum += mFactSum * (mSide ? mRate : (1 / mRate)); + if (mSum < 0.0) { + mSide = ! mSide; + mSum *= (mSide ? mRate : (1 / mRate)); + } } mReady = PR_TRUE; diff --git a/storage/base/aaStateRow.h b/storage/base/aaStateRow.h index eb9d17e..0691eb3 100644 --- a/storage/base/aaStateRow.h +++ b/storage/base/aaStateRow.h @@ -30,7 +30,7 @@ class aaStateRow { public: aaStateRow() :mReady(0) {;} - aaStateRow(mozIStorageStatement *aStatement); + aaStateRow(mozIStorageStatement *aStatement, PRBool isRemoval); ~aaStateRow() { ; } PRBool Adjust(mozIStorageConnection *aConnection); private: -- 2.11.4.GIT