Skip to content

Commit

Permalink
Add JSON validation and range scans.
Browse files Browse the repository at this point in the history
  • Loading branch information
NikitOS94 authored and afiskon committed Oct 19, 2017
1 parent ea03dac commit 755987a
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 18 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
@@ -0,0 +1,3 @@
[submodule "deps/rapidjson"]
path = deps/rapidjson
url = https://github.com/miloyip/rapidjson
20 changes: 19 additions & 1 deletion API.md
Expand Up @@ -47,11 +47,15 @@
* **Successful Response:**

* **Code:** `200` <br />

* **Error Response:**

* **Code:** `400` <br />

* **Example:**

```
curl -v -XPUT -d 'some_value' localhost:8080/v1/kv/some_key
curl -v -XPUT -d '{ "some_subkey" : "some_value" }' localhost:8080/v1/kv/some_key
```


Expand All @@ -74,6 +78,20 @@
curl -v -XGET localhost:8080/v1/kv/some_key
```

## Get range values

`GET /v1/kv/:key/:key`

* **Successful Response:**

* **Code:** `200` <br /> <br />
**Content:** `{"key1":"value1","key2":"value2", ... }`

* **Example:**

```
curl -v -XGET localhost:8080/v1/kv/some_key/some_key2
```

## Delete value

Expand Down
5 changes: 5 additions & 0 deletions CMakeLists.txt
Expand Up @@ -20,6 +20,11 @@ find_library(PCRE_LIBRARY pcre)
find_library(ROCKSDB_LIBRARY rocksdb)

include_directories(include)
include_directories(
include
deps/rapidjson/include
)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED on)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror")
Expand Down
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -38,6 +38,8 @@ Build HurmaDB:
```
git clone https://github.com/afiskon/hurmadb.git
cd hurmadb
git submodule init
git submodule update
mkdir build
cd build
cmake ..
Expand Down
1 change: 1 addition & 0 deletions deps/rapidjson
Submodule rapidjson added at d71ad0
3 changes: 2 additions & 1 deletion include/PersistentStorage.h
Expand Up @@ -14,8 +14,9 @@ class PersistentStorage : public Storage {

PersistentStorage();
~PersistentStorage();
void set(const std::string& key, const std::string& value);
void set(const std::string& key, const std::string& value, bool* append);
std::string get(const std::string& key, bool* found);
std::string getRange(const std::string& key_from, const std::string& key_to);
void del(const std::string& key, bool* found);

PersistentStorage(PersistentStorage const&) = delete;
Expand Down
3 changes: 2 additions & 1 deletion include/Storage.h
Expand Up @@ -6,9 +6,10 @@

class Storage {
public:
virtual void set(const std::string& key, const std::string& value) = 0;
virtual void set(const std::string& key, const std::string& value, bool* append) = 0;
virtual std::string get(const std::string& key, bool* found) = 0;
virtual std::string get(const std::string& key);
virtual std::string getRange(const std::string& key_from, const std::string& key_to) = 0;
virtual void del(const std::string& key, bool* found) = 0;
virtual void del(const std::string& key);
};
18 changes: 8 additions & 10 deletions src/Main.cpp
Expand Up @@ -43,27 +43,25 @@ static void httpKVGetHandler(const HttpRequest& req, HttpResponse& resp) {
resp.setBody(value);
}

/*

static void httpKVGetRangeHandler(const HttpRequest& req, HttpResponse& resp) {
bool found = true;
const std::string& key_from = req.getQueryMatch(0);
const std::string& key_to = req.getQueryMatch(1);
std::string value = std::string("From ") + key_from + ", to " + key_to;
// const std::string& value = storage.get(key, &found);
resp.setStatus(found ? HTTP_STATUS_OK : HTTP_STATUS_NOT_FOUND);
const std::string& value = storage.getRange(key_from, key_to);
resp.setStatus(HTTP_STATUS_OK);
resp.setBody(value);
}
*/

static void httpKVPutHandler(const HttpRequest& req, HttpResponse& resp) {
bool append = false;
const std::string& key = req.getQueryMatch(0);
const std::string& value = req.getBody();
storage.set(key, value);
resp.setStatus(HTTP_STATUS_OK);
storage.set(key, value, &append);
resp.setStatus(append ? HTTP_STATUS_OK : HTTP_STATUS_BAD_REQUEST);
}

static void httpKVDeleteHandler(const HttpRequest& req, HttpResponse& resp) {
bool found;
bool found = false;
const std::string& key = req.getQueryMatch(0);
storage.del(key, &found);
resp.setStatus(found ? HTTP_STATUS_OK : HTTP_STATUS_NOT_FOUND);
Expand Down Expand Up @@ -91,7 +89,7 @@ int main(int argc, char** argv) {
server.addHandler(HTTP_GET, "(?i)^/$", &httpIndexGetHandler);
server.addHandler(HTTP_PUT, "(?i)^/v1/_stop/?$", &httpStopPutHandler);
server.addHandler(HTTP_GET, "(?i)^/v1/kv/([\\w-]+)/?$", &httpKVGetHandler);
// server.addHandler(HTTP_GET, "(?i)^/v1/kv/([\\w-]+)/([\\w-]+)/?$", &httpKVGetRangeHandler);
server.addHandler(HTTP_GET, "(?i)^/v1/kv/([\\w-]+)/([\\w-]+)/?$", &httpKVGetRangeHandler);
server.addHandler(HTTP_PUT, "(?i)^/v1/kv/([\\w-]+)/?$", &httpKVPutHandler);
server.addHandler(HTTP_DELETE, "(?i)^/v1/kv/([\\w-]+)/?$", &httpKVDeleteHandler);

Expand Down
60 changes: 55 additions & 5 deletions src/PersistentStorage.cpp
Expand Up @@ -3,7 +3,11 @@
#include <PersistentStorage.h>
#include <stdexcept>
#include <string>
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
#include <rapidjson/writer.h>

using namespace rapidjson;
using namespace rocksdb;

PersistentStorage::PersistentStorage() {
Expand All @@ -25,10 +29,24 @@ PersistentStorage::~PersistentStorage() {
delete _db;
}

void PersistentStorage::set(const std::string& key, const std::string& value) {
Status s = _db->Put(WriteOptions(), key, value);
if(!s.ok())
throw std::runtime_error("PersistentStore::set() - _db->Put failed");
void PersistentStorage::set(const std::string& key, const std::string& value, bool* append) {

Document val;

if(!val.Parse(value.c_str()).HasParseError()){

StringBuffer sb;
Writer<StringBuffer> writer(sb);

val.Accept(writer);

Status s = _db->Put(WriteOptions(), key, sb.GetString());
*append = s.ok();
if(!s.ok())
throw std::runtime_error("PersistentStore::set() - _db->Put failed");
}
else
*append = false;
}

std::string PersistentStorage::get(const std::string& key, bool* found) {
Expand All @@ -38,7 +56,39 @@ std::string PersistentStorage::get(const std::string& key, bool* found) {
return value;
}

std::string PersistentStorage::getRange(const std::string& key_from, const std::string& key_to) {
std::string key = "";
rocksdb::Iterator* it = _db->NewIterator(rocksdb::ReadOptions());

Document result;
result.SetObject();

for (it->Seek(key_from);
it->Valid() && it->key().ToString() <= key_to;
it->Next()) {
key = it->key().ToString();
Value k(key.c_str(), result.GetAllocator());
Document v;
v.Parse(it->value().ToString().c_str());
Value val(v, result.GetAllocator());
result.AddMember(k, val, result.GetAllocator());
}

StringBuffer sb;
Writer<StringBuffer> writer(sb);
result.Accept(writer);

return sb.GetString();
}

void PersistentStorage::del(const std::string& key, bool* found) {

std::string value = "";
Status check = _db->Get(ReadOptions(), key, &value);
*found = check.ok();
if(!check.ok())
return;
Status s = _db->Delete(WriteOptions(), key);
*found = s.ok(); // always returns true. TODO fix this + fix the test

*found = s.ok();
}
46 changes: 46 additions & 0 deletions tests/run.py
Expand Up @@ -77,6 +77,44 @@ def test_kv(self):
# InMemory backend returns 404, RocksDB backend always returns 200
assert(res.status_code == 200 or res.status_code == 404)

# Test validation of stored value
def test_validation(self):
self.log.debug("Running test_validation")
url = 'http://localhost:{}/v1/kv/S1'.format(PORT)
doc = '"asdag'
# Create a new document
res = requests.put(url, data = doc)
assert(res.status_code == 400)

# Test range_query on the expected result
def test_range_query(self):
self.log.debug("Running test_kv2")
url1 = 'http://localhost:{}/v1/kv/val2'.format(PORT)
doc1 = {'foo':'bar', 'baz':['qux']}
url2 = 'http://localhost:{}/v1/kv/val3'.format(PORT)
doc2 = 'qwerty'
query = 'http://localhost:{}/v1/kv/val1/val4'.format(PORT)

# Create a new document
res = requests.put(url1, json = doc1)
assert(res.status_code == 200)
res = requests.put(url2, json = doc2)
assert(res.status_code == 200)

# Check that we receive expended answer
res = requests.get(query)
planned_text = '{"val2":{"foo":"bar","baz":["qux"]},"val3":"qwerty"}'
assert res.text == planned_text, "Wrong result of query"

# Delete the document
res = requests.delete(url1)
assert(res.status_code == 200)

# Delete the document
res = requests.delete(url2)
assert(res.status_code == 200)


# Test multiple message exchange during one connection
def test_keep_alive(self):
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Expand All @@ -88,3 +126,11 @@ def test_keep_alive(self):
data = conn.recv(1024)
assert(data.startswith(b"HTTP/1.1 200 OK"))
conn.close()

# Test delete of not existed key
def test_delete(self):
self.log.debug("Running test_delete")
url = 'http://localhost:{}/v1/kv/not_existed_key'.format(PORT)
# Delete the document
res = requests.delete(url)
assert(res.status_code == 404)

0 comments on commit 755987a

Please sign in to comment.