diff --git a/.gitignore b/.gitignore
index 6157c1787..37b1e0a57 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,3 +63,7 @@ DerivedData
# Intel Parallel Studio 2013 XE
My Amplifier XE Results - RippleD
+
+# KeyvaDB files
+*.key
+*.val
diff --git a/Builds/VisualStudio2012/RippleD.vcxproj b/Builds/VisualStudio2012/RippleD.vcxproj
index e7e4b5088..49b7b0121 100644
--- a/Builds/VisualStudio2012/RippleD.vcxproj
+++ b/Builds/VisualStudio2012/RippleD.vcxproj
@@ -157,6 +157,18 @@
true
true
+
+ true
+ true
+ true
+ true
+
+
+ true
+ true
+ true
+ true
+
true
true
@@ -1402,6 +1414,8 @@
+
+
@@ -1732,7 +1746,7 @@
Disabled
- _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
ProgramDatabase
false
MultiThreadedDebug
diff --git a/Builds/VisualStudio2012/RippleD.vcxproj.filters b/Builds/VisualStudio2012/RippleD.vcxproj.filters
index 7d7ff164c..0d116ab95 100644
--- a/Builds/VisualStudio2012/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2012/RippleD.vcxproj.filters
@@ -897,6 +897,12 @@
[1] Ripple\ripple_app\node
+
+ [1] Ripple\ripple_app\node
+
+
+ [1] Ripple\ripple_app\node
+
@@ -1674,6 +1680,12 @@
[1] Ripple\ripple_app\node
+
+ [1] Ripple\ripple_app\node
+
+
+ [1] Ripple\ripple_app\node
+
diff --git a/TODO.txt b/TODO.txt
index 908475b5f..70c311938 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -2,6 +2,17 @@
RIPPLE TODO
--------------------------------------------------------------------------------
+Vinnie's Short List (Changes day to day)
+- Convert some Ripple boost unit tests to Beast.
+- Eliminate new technical in NodeStore::Backend
+- Improve NodeObject to construct with just a size.
+- Work on KeyvaDB
+- Finish unit tests and code for Validators
+
+--------------------------------------------------------------------------------
+
+- Rewrite boost program_options in Beast
+
- Examples for different backend key/value config settings
- Unit Test attention
@@ -10,8 +21,6 @@ RIPPLE TODO
- Validations unit test
---------------------------------------------------------------------------------
-
- Replace endian conversion calls with beast calls:
htobe32, be32toh, ntohl, etc...
Start by removing the system headers which provide these routines, if possible
diff --git a/modules/ripple_app/node/ripple_KeyvaDB.cpp b/modules/ripple_app/node/ripple_KeyvaDB.cpp
new file mode 100644
index 000000000..276bb93b8
--- /dev/null
+++ b/modules/ripple_app/node/ripple_KeyvaDB.cpp
@@ -0,0 +1,616 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+class KeyvaDBImp : public KeyvaDB
+{
+private:
+ // These are stored in big endian format in the file.
+
+ // A file offset.
+ typedef int64 FileOffset;
+
+ // Index of a key.
+ typedef int32 KeyIndex;
+
+ // Size of a value.
+ typedef int32 ByteSize;
+
+private:
+ enum
+ {
+ // The size of the fixed area at the beginning of the key file.
+ // This is used to store some housekeeping information like the
+ // key size and version number.
+ //
+ keyFileHeaderBytes = 1024
+ };
+
+ // Accessed by multiple threads
+ struct State
+ {
+ ScopedPointer keyIn;
+ ScopedPointer keyOut;
+ KeyIndex newKeyIndex;
+
+ ScopedPointer valIn;
+ ScopedPointer valOut;
+ FileOffset valFileSize;
+
+ bool hasKeys () const noexcept
+ {
+ return newKeyIndex > 1;
+ }
+ };
+
+ typedef SharedData SharedState;
+
+ // Key records are indexed starting at one.
+ struct KeyRecord
+ {
+ explicit KeyRecord (void* const keyStorage)
+ : key (keyStorage)
+ {
+ }
+
+ // Absolute byte FileOffset in the value file.
+ FileOffset valFileOffset;
+
+ // Size of the corresponding value, in bytes.
+ ByteSize valSize;
+
+ // Key record index of left node, or 0.
+ KeyIndex leftIndex;
+
+ // Key record index of right node, or 0.
+ KeyIndex rightIndex;
+
+ // Points to keyBytes storage of the key.
+ void* const key;
+ };
+
+public:
+ KeyvaDBImp (int keyBytes,
+ File keyPath,
+ File valPath,
+ bool filesAreTemporary)
+ : m_keyBytes (keyBytes)
+ , m_keyRecordBytes (getKeyRecordBytes ())
+ , m_filesAreTemporary (filesAreTemporary)
+ , m_keyStorage (keyBytes)
+ {
+ SharedState::WriteAccess state (m_state);
+
+ // Output must be opened first, in case it has
+ // to created, or else opening for input will fail.
+ state->keyOut = openForWrite (keyPath);
+ state->keyIn = openForRead (keyPath);
+
+ int64 const fileSize = state->keyIn->getFile ().getSize ();
+
+ if (fileSize == 0)
+ {
+ // initialize the key file
+ state->keyOut->setPosition (keyFileHeaderBytes - 1);
+ state->keyOut->writeByte (0);
+ state->keyOut->flush ();
+ }
+
+ state->newKeyIndex = 1 + (state->keyIn->getFile ().getSize () - keyFileHeaderBytes) / m_keyRecordBytes;
+
+ state->valOut = openForWrite (valPath);
+ state->valIn = openForRead (valPath);
+ state->valFileSize = state->valIn->getFile ().getSize ();
+ }
+
+ ~KeyvaDBImp ()
+ {
+ SharedState::WriteAccess state (m_state);
+
+ flushInternal (state);
+
+ state->keyOut = nullptr;
+ state->valOut = nullptr;
+
+ // Delete the database files if requested.
+ //
+ if (m_filesAreTemporary)
+ {
+ {
+ File const path = state->keyIn->getFile ();
+ state->keyIn = nullptr;
+ path.deleteFile ();
+ }
+
+ {
+ File const path = state->valIn->getFile ();
+ state->valIn = nullptr;
+ path.deleteFile ();
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+
+ // Returns the number of physical bytes in a key record.
+ // This is specific to the format of the data.
+ //
+ int getKeyRecordBytes () const noexcept
+ {
+ int bytes = 0;
+
+ bytes += sizeof (FileOffset); // valFileOffset
+ bytes += sizeof (ByteSize); // valSize
+ bytes += sizeof (KeyIndex); // leftIndex
+ bytes += sizeof (KeyIndex); // rightIndex
+
+ bytes += m_keyBytes;
+
+ return bytes;
+ }
+
+ FileOffset calcKeyRecordOffset (KeyIndex keyIndex)
+ {
+ bassert (keyIndex > 0);
+
+ FileOffset const byteOffset = keyFileHeaderBytes + (keyIndex - 1) * m_keyRecordBytes;
+
+ return byteOffset;
+ }
+
+ // Read a key record into memory.
+ void readKeyRecord (KeyRecord* const keyRecord,
+ KeyIndex const keyIndex,
+ SharedState::WriteAccess& state)
+ {
+ FileOffset const byteOffset = calcKeyRecordOffset (keyIndex);
+
+ bool const success = state->keyIn->setPosition (byteOffset);
+
+ if (success)
+ {
+ // This defines the file format!
+ keyRecord->valFileOffset = state->keyIn->readInt64BigEndian ();
+ keyRecord->valSize = state->keyIn->readIntBigEndian ();
+ keyRecord->leftIndex = state->keyIn->readIntBigEndian ();
+ keyRecord->rightIndex = state->keyIn->readIntBigEndian ();
+
+ // Grab the key
+ state->keyIn->read (keyRecord->key, m_keyBytes);
+ }
+ else
+ {
+ String s;
+ s << "KeyvaDB: Seek failed in " << state->valOut->getFile ().getFileName ();
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+ }
+
+ // Write a key record from memory
+ void writeKeyRecord (KeyRecord const& keyRecord,
+ KeyIndex const keyIndex,
+ SharedState::WriteAccess& state,
+ bool includingKey)
+ {
+ FileOffset const byteOffset = calcKeyRecordOffset (keyIndex);
+
+ bool const success = state->keyOut->setPosition (byteOffset);
+
+ if (success)
+ {
+ // This defines the file format!
+ // VFALCO TODO Make OutputStream return the bool errors here
+ //
+ state->keyOut->writeInt64BigEndian (keyRecord.valFileOffset);
+ state->keyOut->writeIntBigEndian (keyRecord.valSize);
+ state->keyOut->writeIntBigEndian (keyRecord.leftIndex);
+ state->keyOut->writeIntBigEndian (keyRecord.rightIndex);
+
+ // Write the key
+ if (includingKey)
+ {
+ bool const success = state->keyOut->write (keyRecord.key, m_keyBytes);
+
+ if (! success)
+ {
+ String s;
+ s << "KeyvaDB: Write failed in " << state->valOut->getFile ().getFileName ();
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+ }
+
+ state->keyOut->flush ();
+ }
+ else
+ {
+ String s;
+ s << "KeyvaDB: Seek failed in " << state->valOut->getFile ().getFileName ();
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+ }
+
+ // Append a value to the value file.
+ void writeValue (void const* const value, ByteSize valueBytes, SharedState::WriteAccess& state)
+ {
+ bool const success = state->valOut->setPosition (state->valFileSize);
+
+ if (success)
+ {
+ bool const success = state->valOut->write (value, static_cast (valueBytes));
+
+ if (! success)
+ {
+ String s;
+ s << "KeyvaDB: Write failed in " << state->valOut->getFile ().getFileName ();
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+
+ state->valFileSize += valueBytes;
+
+ state->valOut->flush ();
+ }
+ else
+ {
+ String s;
+ s << "KeyvaDB: Seek failed in " << state->valOut->getFile ().getFileName ();
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+ }
+
+ //--------------------------------------------------------------------------
+
+ struct FindResult
+ {
+ FindResult (void* const keyStorage)
+ : keyRecord (keyStorage)
+ {
+ }
+
+ int compare; // result of the last comparison
+ KeyIndex keyIndex; // index we looked at last
+ KeyRecord keyRecord; // KeyRecord we looked at last
+ };
+
+ // Find a key. If the key doesn't exist, enough information
+ // is left behind in the result to perform an insertion.
+ //
+ // Returns true if the key was found.
+ //
+ bool find (FindResult* findResult, void const* key, SharedState::WriteAccess& state)
+ {
+ // Not okay to call this with an empty key file!
+ bassert (state->hasKeys ());
+
+ // This performs a standard binary search
+
+ findResult->keyIndex = 1;
+
+ do
+ {
+ readKeyRecord (&findResult->keyRecord, findResult->keyIndex, state);
+
+ findResult->compare = memcmp (key, findResult->keyRecord.key, m_keyBytes);
+
+ if (findResult->compare < 0)
+ {
+ if (findResult->keyRecord.leftIndex != 0)
+ {
+ // Go left
+ findResult->keyIndex = findResult->keyRecord.leftIndex;
+ }
+ else
+ {
+ // Insert position is to the left
+ break;
+ }
+ }
+ else if (findResult->compare > 0)
+ {
+ if (findResult->keyRecord.rightIndex != 0)
+ {
+ // Go right
+ findResult->keyIndex = findResult->keyRecord.rightIndex;
+ }
+ else
+ {
+ // Insert position is to the right
+ break;
+ }
+ }
+ }
+ while (findResult->compare != 0);
+
+ return findResult->compare == 0;
+ }
+
+ //--------------------------------------------------------------------------
+
+ bool get (void const* key, GetCallback* callback)
+ {
+ FindResult findResult (m_keyStorage.getData ());
+
+ SharedState::WriteAccess state (m_state);
+
+ bool found = false;
+
+ if (state->hasKeys ())
+ {
+ found = find (&findResult, key, state);
+
+ if (found)
+ {
+ void* const destStorage = callback->createStorageForValue (findResult.keyRecord.valSize);
+
+ bool const success = state->valIn->setPosition (findResult.keyRecord.valFileOffset);
+
+ if (! success)
+ {
+ String s;
+ s << "KeyvaDB: Seek failed in " << state->valOut->getFile ().getFileName ();
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+
+ int const bytesRead = state->valIn->read (destStorage, findResult.keyRecord.valSize);
+
+ if (bytesRead != findResult.keyRecord.valSize)
+ {
+ String s;
+ s << "KeyvaDB: Couldn't read a value from " << state->valIn->getFile ().getFileName ();
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+ }
+ }
+
+ return found;
+ }
+
+ //--------------------------------------------------------------------------
+
+ void put (void const* key, void const* value, int valueBytes)
+ {
+ bassert (valueBytes > 0);
+
+ SharedState::WriteAccess state (m_state);
+
+ if (state->hasKeys ())
+ {
+ // Search for the key
+
+ FindResult findResult (m_keyStorage.getData ());
+
+ bool const found = find (&findResult, key, state);
+
+ if (! found )
+ {
+ bassert (findResult.compare != 0);
+
+ // Binary tree insertion.
+ // Link the last key record to the new key
+ {
+ if (findResult.compare == -1)
+ {
+ findResult.keyRecord.leftIndex = state->newKeyIndex;
+ }
+ else
+ {
+ findResult.keyRecord.rightIndex = state->newKeyIndex;
+ }
+
+ writeKeyRecord (findResult.keyRecord, findResult.keyIndex, state, false);
+ }
+
+ // Write the new key
+ {
+ findResult.keyRecord.valFileOffset = state->valFileSize;
+ findResult.keyRecord.valSize = valueBytes;
+ findResult.keyRecord.leftIndex = 0;
+ findResult.keyRecord.rightIndex = 0;
+
+ memcpy (findResult.keyRecord.key, key, m_keyBytes);
+
+ writeKeyRecord (findResult.keyRecord, state->newKeyIndex, state, true);
+ }
+
+ // Key file has grown by one.
+ ++state->newKeyIndex;
+
+ // Write the value
+ writeValue (value, valueBytes, state);
+ }
+ else
+ {
+ String s;
+ s << "KeyvaDB: Attempt to write a duplicate key!";
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+ }
+ else
+ {
+ //
+ // Write first key
+ //
+
+ KeyRecord keyRecord (m_keyStorage.getData ());
+
+ keyRecord.valFileOffset = state->valFileSize;
+ keyRecord.valSize = valueBytes;
+ keyRecord.leftIndex = 0;
+ keyRecord.rightIndex = 0;
+
+ memcpy (keyRecord.key, key, m_keyBytes);
+
+ writeKeyRecord (keyRecord, state->newKeyIndex, state, true);
+
+ // Key file has grown by one.
+ ++state->newKeyIndex;
+
+ //
+ // Write value
+ //
+
+ bassert (state->valFileSize == 0);
+
+ writeValue (value, valueBytes, state);
+ }
+ }
+
+ //--------------------------------------------------------------------------
+
+ void flush ()
+ {
+ SharedState::WriteAccess state (m_state);
+
+ flushInternal (state);
+ }
+
+ void flushInternal (SharedState::WriteAccess& state)
+ {
+ state->keyOut->flush ();
+ state->valOut->flush ();
+ }
+
+ //--------------------------------------------------------------------------
+
+private:
+ // Open a file for reading.
+ static FileInputStream* openForRead (File path)
+ {
+ FileInputStream* stream = path.createInputStream ();
+
+ if (stream == nullptr)
+ {
+ String s;
+ s << "KeyvaDB: Couldn't open " << path.getFileName () << " for reading.";
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+
+ return stream;
+ }
+
+ // Open a file for writing.
+ static FileOutputStream* openForWrite (File path)
+ {
+ FileOutputStream* stream = path.createOutputStream ();
+
+ if (stream == nullptr)
+ {
+ String s;
+ s << "KeyvaDB: Couldn't open " << path.getFileName () << " for writing.";
+ Throw (std::runtime_error (s.toStdString ()));
+ }
+
+ return stream;
+ }
+
+private:
+ int const m_keyBytes;
+ int const m_keyRecordBytes;
+ bool const m_filesAreTemporary;
+ SharedState m_state;
+ HeapBlock m_keyStorage;
+};
+
+KeyvaDB* KeyvaDB::New (int keyBytes, File keyPath, File valPath, bool filesAreTemporary)
+{
+ return new KeyvaDBImp (keyBytes, keyPath, valPath, filesAreTemporary);
+}
+
+//------------------------------------------------------------------------------
+
+class KeyvaDBTests : public UnitTest
+{
+public:
+ KeyvaDBTests () : UnitTest ("KevyaDB")
+ {
+ }
+
+ template
+ void repeatableShuffle (int const numberOfItems, HeapBlock & items)
+ {
+ Random r (69);
+
+ for (int i = numberOfItems - 1; i > 0; --i)
+ {
+ int const choice = r.nextInt (i + 1);
+
+ std::swap (items [i], items [choice]);
+ }
+ }
+
+ template
+ void testSize (unsigned int const maxItems)
+ {
+ typedef UnsignedInteger KeyType;
+
+ String s;
+ s << "keyBytes=" << String (KeyBytes);
+ beginTest (s);
+
+ // Set up the key and value files and open the db.
+ File const keyPath = File::createTempFile ("").withFileExtension (".key");
+ File const valPath = File::createTempFile ("").withFileExtension (".val");
+ ScopedPointer db (KeyvaDB::New (KeyBytes, keyPath, valPath, true));
+
+ {
+ // Create an array of ascending integers.
+ HeapBlock items (maxItems);
+ for (unsigned int i = 0; i < maxItems; ++i)
+ items [i] = i;
+
+ // Now shuffle it deterministically.
+ repeatableShuffle (maxItems, items);
+
+ // Write all the keys of integers.
+ for (unsigned int i = 0; i < maxItems; ++i)
+ {
+ unsigned int const num = items [i];
+ KeyType const v = KeyType::createFromInteger (num);
+
+ // The value is the same as the key, for ease of comparison.
+ db->put (v.cbegin (), v.cbegin (), KeyBytes);
+ }
+ }
+
+ {
+ // This callback creates storage for the value.
+ struct MyGetCallback : KeyvaDB::GetCallback
+ {
+ KeyType v;
+
+ void* createStorageForValue (int valueBytes)
+ {
+ bassert (valueBytes == KeyBytes);
+
+ return v.begin ();
+ }
+ };
+
+ // Go through all of our keys and try to retrieve them.
+ // since this is done in ascending order, we should get
+ // random seeks at this point.
+ //
+ for (unsigned int i = 0; i < maxItems; ++i)
+ {
+ KeyType const v = KeyType::createFromInteger (i);
+
+ MyGetCallback cb;
+
+ bool const found = db->get (v.cbegin (), &cb);
+
+ expect (found, "Should be found");
+
+ expect (v == cb.v, "Should be equal");
+ }
+ }
+ }
+
+ void runTest ()
+ {
+ testSize <4> (512);
+ testSize <32> (4096);
+ }
+};
+
+static KeyvaDBTests keyvaDBTests;
diff --git a/modules/ripple_app/node/ripple_KeyvaDB.h b/modules/ripple_app/node/ripple_KeyvaDB.h
new file mode 100644
index 000000000..9ff1b2ec2
--- /dev/null
+++ b/modules/ripple_app/node/ripple_KeyvaDB.h
@@ -0,0 +1,35 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_KEYVADB_H_INCLUDED
+#define RIPPLE_KEYVADB_H_INCLUDED
+
+/** Key/value database optimized for Ripple usage.
+*/
+class KeyvaDB : LeakChecked
+{
+public:
+ class GetCallback
+ {
+ public:
+ virtual void* createStorageForValue (int valueBytes) = 0;
+ };
+
+ static KeyvaDB* New (int keyBytes,
+ File keyPath,
+ File valPath,
+ bool filesAreTemporary);
+
+ virtual ~KeyvaDB () { }
+
+ virtual bool get (void const* key, GetCallback* callback) = 0;
+
+ virtual void put (void const* key, void const* value, int valueBytes) = 0;
+
+ virtual void flush () = 0;
+};
+
+#endif
diff --git a/modules/ripple_app/node/ripple_KeyvaDBBackendFactory.cpp b/modules/ripple_app/node/ripple_KeyvaDBBackendFactory.cpp
new file mode 100644
index 000000000..865bafaad
--- /dev/null
+++ b/modules/ripple_app/node/ripple_KeyvaDBBackendFactory.cpp
@@ -0,0 +1,149 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+class KeyvaDBBackendFactory::Backend : public NodeStore::Backend
+{
+public:
+ typedef UnsignedInteger <32> Key;
+
+ enum
+ {
+ keyBytes = Key::sizeInBytes
+ };
+
+ explicit Backend (StringPairArray const& keyValues)
+ : m_path (keyValues ["path"])
+ , m_db (KeyvaDB::New (
+ keyBytes,
+ File::getCurrentWorkingDirectory().getChildFile (m_path).withFileExtension ("key"),
+ File::getCurrentWorkingDirectory().getChildFile (m_path).withFileExtension ("val"),
+ false))
+ {
+ }
+
+ ~Backend ()
+ {
+ }
+
+ std::string getDataBaseName ()
+ {
+ return m_path.toStdString ();
+ }
+
+ void writeObject (NodeObject const& object)
+ {
+ m_db->put (
+ object.getHash ().begin (),
+ &object.getData () [0],
+ object.getData ().size ());
+ }
+
+ bool bulkStore (std::vector const& objs)
+ {
+ for (size_t i = 0; i < objs.size (); ++i)
+ {
+ writeObject (*objs [i]);
+ }
+
+ return true;
+ }
+
+ struct MyGetCallback : KeyvaDB::GetCallback
+ {
+ int valueBytes;
+ HeapBlock data;
+
+ void* createStorageForValue (int valueBytes_)
+ {
+ valueBytes = valueBytes_;
+
+ data.malloc (valueBytes);
+
+ return data.getData ();
+ }
+ };
+
+ NodeObject::pointer retrieve (uint256 const& hash)
+ {
+ NodeObject::pointer result;
+
+ MyGetCallback cb;
+
+ bool const found = m_db->get (hash.begin (), &cb);
+
+ if (found)
+ {
+ result = fromBinary (hash, cb.data.getData (), cb.valueBytes);
+ }
+
+ return result;
+ }
+
+ void visitAll (FUNCTION_TYPE func)
+ {
+ bassertfalse;
+ }
+
+ Blob toBlob (NodeObject::ref obj)
+ {
+ Blob rawData (9 + obj->getData ().size ());
+ unsigned char* bufPtr = &rawData.front();
+
+ *reinterpret_cast (bufPtr + 0) = ntohl (obj->getIndex ());
+ *reinterpret_cast (bufPtr + 4) = ntohl (obj->getIndex ());
+ * (bufPtr + 8) = static_cast (obj->getType ());
+ memcpy (bufPtr + 9, &obj->getData ().front (), obj->getData ().size ());
+
+ return rawData;
+ }
+
+ NodeObject::pointer fromBinary (uint256 const& hash, char const* data, int size)
+ {
+ if (size < 9)
+ throw std::runtime_error ("undersized object");
+
+ uint32 index = htonl (*reinterpret_cast (data));
+
+ int htype = data[8];
+
+ return boost::make_shared (static_cast (htype), index,
+ data + 9, size - 9, hash);
+ }
+
+private:
+ String m_path;
+ ScopedPointer m_db;
+};
+
+//------------------------------------------------------------------------------
+
+KeyvaDBBackendFactory::KeyvaDBBackendFactory ()
+{
+}
+
+KeyvaDBBackendFactory::~KeyvaDBBackendFactory ()
+{
+}
+
+KeyvaDBBackendFactory& KeyvaDBBackendFactory::getInstance ()
+{
+ static KeyvaDBBackendFactory instance;
+
+ return instance;
+}
+
+String KeyvaDBBackendFactory::getName () const
+{
+ return "KeyvaDB";
+}
+
+NodeStore::Backend* KeyvaDBBackendFactory::createInstance (StringPairArray const& keyValues)
+{
+ return new KeyvaDBBackendFactory::Backend (keyValues);
+}
+
+//------------------------------------------------------------------------------
+
diff --git a/modules/ripple_app/node/ripple_KeyvaDBBackendFactory.h b/modules/ripple_app/node/ripple_KeyvaDBBackendFactory.h
new file mode 100644
index 000000000..2587315d8
--- /dev/null
+++ b/modules/ripple_app/node/ripple_KeyvaDBBackendFactory.h
@@ -0,0 +1,27 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_KEYVABACKENDFACTORY_H_INCLUDED
+#define RIPPLE_KEYVABACKENDFACTORY_H_INCLUDED
+
+/** Factory to produce KeyvaDB backends for the NodeStore.
+*/
+class KeyvaDBBackendFactory : public NodeStore::BackendFactory
+{
+private:
+ class Backend;
+
+ KeyvaDBBackendFactory ();
+ ~KeyvaDBBackendFactory ();
+
+public:
+ static KeyvaDBBackendFactory& getInstance ();
+
+ String getName () const;
+ NodeStore::Backend* createInstance (StringPairArray const& keyValues);
+};
+
+#endif
diff --git a/modules/ripple_app/ripple_app.cpp b/modules/ripple_app/ripple_app.cpp
index afd567e70..43f9e5b4a 100644
--- a/modules/ripple_app/ripple_app.cpp
+++ b/modules/ripple_app/ripple_app.cpp
@@ -104,6 +104,7 @@ namespace ripple
#include "node/ripple_NodeStore.h"
#include "node/ripple_LevelDBBackendFactory.h"
#include "node/ripple_HyperLevelDBBackendFactory.h"
+#include "node/ripple_KeyvaDBBackendFactory.h"
#include "node/ripple_MdbBackendFactory.h"
#include "node/ripple_NullBackendFactory.h"
#include "node/ripple_SqliteBackendFactory.h"
@@ -249,6 +250,9 @@ static const uint64 tenTo17m1 = tenTo17 - 1;
#include "node/ripple_MdbBackendFactory.cpp"
#include "node/ripple_NullBackendFactory.cpp"
#include "node/ripple_SqliteBackendFactory.cpp"
+#include "node/ripple_KeyvaDB.h" // private
+#include "node/ripple_KeyvaDB.cpp"
+#include "node/ripple_KeyvaDBBackendFactory.cpp"
#include "ledger/Ledger.cpp"
#include "src/cpp/ripple/ripple_SHAMapDelta.cpp"
diff --git a/src/cpp/ripple/ripple_Main.cpp b/src/cpp/ripple/ripple_Main.cpp
index 606453a4f..2384ca412 100644
--- a/src/cpp/ripple/ripple_Main.cpp
+++ b/src/cpp/ripple/ripple_Main.cpp
@@ -155,7 +155,7 @@ static void runBeastUnitTests ()
{
UnitTests::TestResult const& r (*tr.getResult (i));
- for (int j = 0; j < r.messages.size (); ++i)
+ for (int j = 0; j < r.messages.size (); ++j)
Log::out () << r.messages [j].toStdString ();
}
}
@@ -252,6 +252,7 @@ int rippleMain (int argc, char** argv)
// These must be added before the Application object is created
NodeStore::addBackendFactory (SqliteBackendFactory::getInstance ());
NodeStore::addBackendFactory (LevelDBBackendFactory::getInstance ());
+ NodeStore::addBackendFactory (KeyvaDBBackendFactory::getInstance ());
#if RIPPLE_HYPERLEVELDB_AVAILABLE
NodeStore::addBackendFactory (HyperLevelDBBackendFactory::getInstance ());
#endif