Summary: Completed the implementation for the Redis API for Lists. The Redis API uses rocksdb as a backend to persistently store maps from key->list. It supports basic operations for appending, inserting, pushing, popping, and accessing a list, given its key. Test Plan: - Compile with: make redis_test - Test with: ./redis_test - Run all unit tests (for all rocksdb) with: make all check - To use an interactive REDIS client use: ./redis_test -m - To clean the database before use: ./redis_test -m -d Reviewers: haobo, dhruba, zshao Reviewed By: haobo CC: leveldb Differential Revision: https://reviews.facebook.net/D10833main
parent
e673d5d26d
commit
5679107b07
@ -0,0 +1,14 @@ |
|||||||
|
This folder defines a REDIS-style interface for Rocksdb. |
||||||
|
Right now it is written as a simple tag-on in the leveldb::RedisLists class. |
||||||
|
It implements Redis Lists, and supports only the "non-blocking operations". |
||||||
|
|
||||||
|
Internally, the set of lists are stored in a rocksdb database, mapping keys to |
||||||
|
values. Each "value" is the list itself, storing a sequence of "elements". |
||||||
|
Each element is stored as a 32-bit-integer, followed by a sequence of bytes. |
||||||
|
The 32-bit-integer represents the length of the element (that is, the number |
||||||
|
of bytes that follow). And then that many bytes follow. |
||||||
|
|
||||||
|
|
||||||
|
NOTE: This README file may be old. See the actual redis_lists.cc file for |
||||||
|
definitive details on the implementation. There should be a header at the top |
||||||
|
of that file, explaining a bit of the implementation details. |
@ -0,0 +1,24 @@ |
|||||||
|
/**
|
||||||
|
* A simple structure for exceptions in RedisLists. |
||||||
|
* |
||||||
|
* @author Deon Nicholas (dnicholas@fb.com) |
||||||
|
* Copyright 2013 Facebook |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef LEVELDB_REDIS_LIST_EXCEPTION_H |
||||||
|
#define LEVELDB_REDIS_LIST_EXCEPTION_H |
||||||
|
|
||||||
|
#include <exception> |
||||||
|
|
||||||
|
namespace leveldb { |
||||||
|
|
||||||
|
class RedisListException: public std::exception { |
||||||
|
public: |
||||||
|
const char* what() const throw() { |
||||||
|
return "Invalid operation or corrupt data in Redis List."; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace leveldb
|
||||||
|
|
||||||
|
#endif // LEVELDB_REDIS_LIST_EXCEPTION_H
|
@ -0,0 +1,306 @@ |
|||||||
|
/**
|
||||||
|
* RedisListIterator: |
||||||
|
* An abstraction over the "list" concept (e.g.: for redis lists). |
||||||
|
* Provides functionality to read, traverse, edit, and write these lists. |
||||||
|
* |
||||||
|
* Upon construction, the RedisListIterator is given a block of list data. |
||||||
|
* Internally, it stores a pointer to the data and a pointer to current item. |
||||||
|
* It also stores a "result" list that will be mutated over time. |
||||||
|
* |
||||||
|
* Traversal and mutation are done by "forward iteration". |
||||||
|
* The Push() and Skip() methods will advance the iterator to the next item. |
||||||
|
* However, Push() will also "write the current item to the result". |
||||||
|
* Skip() will simply move to next item, causing current item to be dropped. |
||||||
|
* |
||||||
|
* Upon completion, the result (accessible by WriteResult()) will be saved. |
||||||
|
* All "skipped" items will be gone; all "pushed" items will remain. |
||||||
|
* |
||||||
|
* @throws Any of the operations may throw a RedisListException if an invalid |
||||||
|
* operation is performed or if the data is found to be corrupt. |
||||||
|
* |
||||||
|
* @notes By default, if WriteResult() is called part-way through iteration, |
||||||
|
* it will automatically advance the iterator to the end, and Keep() |
||||||
|
* all items that haven't been traversed yet. This may be subject |
||||||
|
* to review. |
||||||
|
* |
||||||
|
* @notes Can access the "current" item via GetCurrent(), and other |
||||||
|
* list-specific information such as Length(). |
||||||
|
* |
||||||
|
* @notes The internal representation is due to change at any time. Presently, |
||||||
|
* the list is represented as follows: |
||||||
|
* - 32-bit integer header: the number of items in the list |
||||||
|
* - For each item: |
||||||
|
* - 32-bit int (n): the number of bytes representing this item |
||||||
|
* - n bytes of data: the actual data. |
||||||
|
* |
||||||
|
* @author Deon Nicholas (dnicholas@fb.com) |
||||||
|
* Copyright 2013 Facebook |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "redis_list_exception.h" |
||||||
|
#include "leveldb/slice.h" |
||||||
|
#include "util/coding.h" |
||||||
|
|
||||||
|
namespace leveldb { |
||||||
|
|
||||||
|
/// An abstraction over the "list" concept.
|
||||||
|
/// All operations may throw a RedisListException
|
||||||
|
class RedisListIterator { |
||||||
|
public: |
||||||
|
/// Construct a redis-list-iterator based on data.
|
||||||
|
/// If the data is non-empty, it must formatted according to @notes above.
|
||||||
|
///
|
||||||
|
/// If the data is valid, we can assume the following invariant(s):
|
||||||
|
/// a) length_, num_bytes_ are set correctly.
|
||||||
|
/// b) cur_byte_ always refers to the start of the current element,
|
||||||
|
/// just before the bytes that specify element length.
|
||||||
|
/// c) cur_elem_ is always the index of the current element.
|
||||||
|
/// d) cur_elem_length_ is always the number of bytes in current element,
|
||||||
|
/// excluding the 4-byte header itself.
|
||||||
|
/// e) result_ will always contain data_[0..cur_byte_) and a header
|
||||||
|
/// f) Whenever corrupt data is encountered or an invalid operation is
|
||||||
|
/// attempted, a RedisListException will immediately be thrown.
|
||||||
|
RedisListIterator(const std::string& list_data) |
||||||
|
: data_(list_data.data()), |
||||||
|
num_bytes_(list_data.size()), |
||||||
|
cur_byte_(0), |
||||||
|
cur_elem_(0), |
||||||
|
cur_elem_length_(0), |
||||||
|
length_(0), |
||||||
|
result_() { |
||||||
|
|
||||||
|
// Initialize the result_ (reserve enough space for header)
|
||||||
|
InitializeResult(); |
||||||
|
|
||||||
|
// Parse the data only if it is not empty.
|
||||||
|
if (num_bytes_ == 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// If non-empty, but less than 4 bytes, data must be corrupt
|
||||||
|
if (num_bytes_ < sizeof(length_)) { |
||||||
|
ThrowError("Corrupt header."); // Will break control flow
|
||||||
|
} |
||||||
|
|
||||||
|
// Good. The first bytes specify the number of elements
|
||||||
|
length_ = DecodeFixed32(data_); |
||||||
|
cur_byte_ = sizeof(length_); |
||||||
|
|
||||||
|
// If we have at least one element, point to that element.
|
||||||
|
// Also, read the first integer of the element (specifying the size),
|
||||||
|
// if possible.
|
||||||
|
if (length_ > 0) { |
||||||
|
if (cur_byte_ + sizeof(cur_elem_length_) <= num_bytes_) { |
||||||
|
cur_elem_length_ = DecodeFixed32(data_+cur_byte_); |
||||||
|
} else { |
||||||
|
ThrowError("Corrupt data for first element."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// At this point, we are fully set-up.
|
||||||
|
// The invariants described in the header should now be true.
|
||||||
|
} |
||||||
|
|
||||||
|
/// Reserve some space for the result_.
|
||||||
|
/// Equivalent to result_.reserve(bytes).
|
||||||
|
void Reserve(int bytes) { |
||||||
|
result_.reserve(bytes); |
||||||
|
} |
||||||
|
|
||||||
|
/// Go to next element in data file.
|
||||||
|
/// Also writes the current element to result_.
|
||||||
|
RedisListIterator& Push() { |
||||||
|
WriteCurrentElement(); |
||||||
|
MoveNext(); |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
/// Go to next element in data file.
|
||||||
|
/// Drops/skips the current element. It will not be written to result_.
|
||||||
|
RedisListIterator& Skip() { |
||||||
|
MoveNext(); |
||||||
|
--length_; // One less item
|
||||||
|
--cur_elem_; // We moved one forward, but index did not change
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
/// Insert elem into the result_ (just BEFORE the current element / byte)
|
||||||
|
/// Note: if Done() (i.e.: iterator points to end), this will append elem.
|
||||||
|
void InsertElement(const Slice& elem) { |
||||||
|
// Ensure we are in a valid state
|
||||||
|
CheckErrors(); |
||||||
|
|
||||||
|
const int kOrigSize = result_.size(); |
||||||
|
result_.resize(kOrigSize + SizeOf(elem)); |
||||||
|
EncodeFixed32(result_.data() + kOrigSize, elem.size()); |
||||||
|
memcpy(result_.data() + kOrigSize + sizeof(uint32_t), |
||||||
|
elem.data(), |
||||||
|
elem.size()); |
||||||
|
++length_; |
||||||
|
++cur_elem_; |
||||||
|
} |
||||||
|
|
||||||
|
/// Access the current element, and save the result into *curElem
|
||||||
|
void GetCurrent(Slice* curElem) { |
||||||
|
// Ensure we are in a valid state
|
||||||
|
CheckErrors(); |
||||||
|
|
||||||
|
// Ensure that we are not past the last element.
|
||||||
|
if (Done()) { |
||||||
|
ThrowError("Invalid dereferencing."); |
||||||
|
} |
||||||
|
|
||||||
|
// Dereference the element
|
||||||
|
*curElem = Slice(data_+cur_byte_+sizeof(cur_elem_length_), |
||||||
|
cur_elem_length_); |
||||||
|
} |
||||||
|
|
||||||
|
// Number of elements
|
||||||
|
int Length() const { |
||||||
|
return length_; |
||||||
|
} |
||||||
|
|
||||||
|
// Number of bytes in the final representation (i.e: WriteResult().size())
|
||||||
|
int Size() const { |
||||||
|
// result_ holds the currently written data
|
||||||
|
// data_[cur_byte..num_bytes-1] is the remainder of the data
|
||||||
|
return result_.size() + (num_bytes_ - cur_byte_); |
||||||
|
} |
||||||
|
|
||||||
|
// Reached the end?
|
||||||
|
bool Done() const { |
||||||
|
return cur_byte_ >= num_bytes_ || cur_elem_ >= length_; |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns a string representing the final, edited, data.
|
||||||
|
/// Assumes that all bytes of data_ in the range [0,cur_byte_) have been read
|
||||||
|
/// and that result_ contains this data.
|
||||||
|
/// The rest of the data must still be written.
|
||||||
|
/// So, this method ADVANCES THE ITERATOR TO THE END before writing.
|
||||||
|
Slice WriteResult() { |
||||||
|
CheckErrors(); |
||||||
|
|
||||||
|
// The header should currently be filled with dummy data (0's)
|
||||||
|
// Correctly update the header.
|
||||||
|
// Note, this is safe since result_ is a vector (guaranteed contiguous)
|
||||||
|
EncodeFixed32(&result_[0],length_); |
||||||
|
|
||||||
|
// Append the remainder of the data to the result.
|
||||||
|
result_.insert(result_.end(),data_+cur_byte_, data_ +num_bytes_); |
||||||
|
|
||||||
|
// Seek to end of file
|
||||||
|
cur_byte_ = num_bytes_; |
||||||
|
cur_elem_ = length_; |
||||||
|
cur_elem_length_ = 0; |
||||||
|
|
||||||
|
// Return the result
|
||||||
|
return Slice(result_.data(),result_.size()); |
||||||
|
} |
||||||
|
|
||||||
|
public: // Static public functions
|
||||||
|
|
||||||
|
/// An upper-bound on the amount of bytes needed to store this element.
|
||||||
|
/// This is used to hide representation information from the client.
|
||||||
|
/// E.G. This can be used to compute the bytes we want to Reserve().
|
||||||
|
static uint32_t SizeOf(const Slice& elem) { |
||||||
|
// [Integer Length . Data]
|
||||||
|
return sizeof(uint32_t) + elem.size(); |
||||||
|
} |
||||||
|
|
||||||
|
private: // Private functions
|
||||||
|
|
||||||
|
/// Initializes the result_ string.
|
||||||
|
/// It will fill the first few bytes with 0's so that there is
|
||||||
|
/// enough space for header information when we need to write later.
|
||||||
|
/// Currently, "header information" means: the length (number of elements)
|
||||||
|
/// Assumes that result_ is empty to begin with
|
||||||
|
void InitializeResult() { |
||||||
|
assert(result_.empty()); // Should always be true.
|
||||||
|
result_.resize(sizeof(uint32_t),0); // Put a block of 0's as the header
|
||||||
|
} |
||||||
|
|
||||||
|
/// Go to the next element (used in Push() and Skip())
|
||||||
|
void MoveNext() { |
||||||
|
CheckErrors(); |
||||||
|
|
||||||
|
// Check to make sure we are not already in a finished state
|
||||||
|
if (Done()) { |
||||||
|
ThrowError("Attempting to iterate past end of list."); |
||||||
|
} |
||||||
|
|
||||||
|
// Move forward one element.
|
||||||
|
cur_byte_ += sizeof(cur_elem_length_) + cur_elem_length_; |
||||||
|
++cur_elem_; |
||||||
|
|
||||||
|
// If we are at the end, finish
|
||||||
|
if (Done()) { |
||||||
|
cur_elem_length_ = 0; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Otherwise, we should be able to read the new element's length
|
||||||
|
if (cur_byte_ + sizeof(cur_elem_length_) > num_bytes_) { |
||||||
|
ThrowError("Corrupt element data."); |
||||||
|
} |
||||||
|
|
||||||
|
// Set the new element's length
|
||||||
|
cur_elem_length_ = DecodeFixed32(data_+cur_byte_); |
||||||
|
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
/// Append the current element (pointed to by cur_byte_) to result_
|
||||||
|
/// Assumes result_ has already been reserved appropriately.
|
||||||
|
void WriteCurrentElement() { |
||||||
|
// First verify that the iterator is still valid.
|
||||||
|
CheckErrors(); |
||||||
|
if (Done()) { |
||||||
|
ThrowError("Attempting to write invalid element."); |
||||||
|
} |
||||||
|
|
||||||
|
// Append the cur element.
|
||||||
|
result_.insert(result_.end(), |
||||||
|
data_+cur_byte_, |
||||||
|
data_+cur_byte_+ sizeof(uint32_t) + cur_elem_length_); |
||||||
|
} |
||||||
|
|
||||||
|
/// Will ThrowError() if neccessary.
|
||||||
|
/// Checks for common/ubiquitous errors that can arise after most operations.
|
||||||
|
/// This method should be called before any reading operation.
|
||||||
|
/// If this function succeeds, then we are guaranteed to be in a valid state.
|
||||||
|
/// Other member functions should check for errors and ThrowError() also
|
||||||
|
/// if an error occurs that is specific to it even while in a valid state.
|
||||||
|
void CheckErrors() { |
||||||
|
// Check if any crazy thing has happened recently
|
||||||
|
if ((cur_elem_ > length_) || // Bad index
|
||||||
|
(cur_byte_ > num_bytes_) || // No more bytes
|
||||||
|
(cur_byte_ + cur_elem_length_ > num_bytes_) || // Item too large
|
||||||
|
(cur_byte_ == num_bytes_ && cur_elem_ != length_) || // Too many items
|
||||||
|
(cur_elem_ == length_ && cur_byte_ != num_bytes_)) { // Too many bytes
|
||||||
|
ThrowError("Corrupt data."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Will throw an exception based on the passed-in message.
|
||||||
|
/// This function is guaranteed to STOP THE CONTROL-FLOW.
|
||||||
|
/// (i.e.: you do not have to call "return" after calling ThrowError)
|
||||||
|
void ThrowError(const char* const msg = NULL) { |
||||||
|
// TODO: For now we ignore the msg parameter. This can be expanded later.
|
||||||
|
throw RedisListException(); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
const char* const data_; // A pointer to the data (the first byte)
|
||||||
|
const uint32_t num_bytes_; // The number of bytes in this list
|
||||||
|
|
||||||
|
uint32_t cur_byte_; // The current byte being read
|
||||||
|
uint32_t cur_elem_; // The current element being read
|
||||||
|
uint32_t cur_elem_length_; // The number of bytes in current element
|
||||||
|
|
||||||
|
uint32_t length_; // The number of elements in this list
|
||||||
|
std::vector<char> result_; // The output data
|
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace leveldb
|
@ -0,0 +1,551 @@ |
|||||||
|
/**
|
||||||
|
* A (persistent) Redis API built using the rocksdb backend. |
||||||
|
* Implements Redis Lists as described on: http://redis.io/commands#list
|
||||||
|
* |
||||||
|
* @throws All functions may throw a RedisListException on error/corruption. |
||||||
|
* |
||||||
|
* @notes Internally, the set of lists is stored in a rocksdb database, |
||||||
|
* mapping keys to values. Each "value" is the list itself, storing |
||||||
|
* some kind of internal representation of the data. All the |
||||||
|
* representation details are handled by the RedisListIterator class. |
||||||
|
* The present file should be oblivious to the representation details, |
||||||
|
* handling only the client (Redis) API, and the calls to rocksdb. |
||||||
|
* |
||||||
|
* @TODO Presently, all operations take at least O(NV) time where |
||||||
|
* N is the number of elements in the list, and V is the average |
||||||
|
* number of bytes per value in the list. So maybe, with merge operator |
||||||
|
* we can improve this to an optimal O(V) amortized time, since we |
||||||
|
* wouldn't have to read and re-write the entire list. |
||||||
|
* |
||||||
|
* @author Deon Nicholas (dnicholas@fb.com) |
||||||
|
* Copyright 2013 Facebook |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "redis_lists.h" |
||||||
|
|
||||||
|
#include <iostream> |
||||||
|
#include <memory> |
||||||
|
#include <cmath> |
||||||
|
|
||||||
|
#include "leveldb/slice.h" |
||||||
|
#include "util/coding.h" |
||||||
|
|
||||||
|
namespace leveldb |
||||||
|
{ |
||||||
|
|
||||||
|
/// Constructors
|
||||||
|
|
||||||
|
RedisLists::RedisLists(const std::string& db_path, |
||||||
|
Options options, bool destructive) |
||||||
|
: put_option_(), |
||||||
|
get_option_() { |
||||||
|
|
||||||
|
// Store the name of the database
|
||||||
|
db_name_ = db_path; |
||||||
|
|
||||||
|
// If destructive, destroy the DB before re-opening it.
|
||||||
|
if (destructive) { |
||||||
|
DestroyDB(db_name_, Options()); |
||||||
|
} |
||||||
|
|
||||||
|
// Now open and deal with the db
|
||||||
|
DB* db; |
||||||
|
Status s = DB::Open(options, db_name_, &db); |
||||||
|
if (!s.ok()) { |
||||||
|
std::cerr << "ERROR " << s.ToString() << std::endl; |
||||||
|
assert(false); |
||||||
|
} |
||||||
|
|
||||||
|
db_ = std::unique_ptr<DB>(db); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/// Accessors
|
||||||
|
|
||||||
|
// Number of elements in the list associated with key
|
||||||
|
// : throws RedisListException
|
||||||
|
int RedisLists::Length(const std::string& key) { |
||||||
|
// Extract the string data representing the list.
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Return the length
|
||||||
|
RedisListIterator it(data); |
||||||
|
return it.Length(); |
||||||
|
} |
||||||
|
|
||||||
|
// Get the element at the specified index in the (list: key)
|
||||||
|
// Returns <empty> ("") on out-of-bounds
|
||||||
|
// : throws RedisListException
|
||||||
|
bool RedisLists::Index(const std::string& key, int32_t index, |
||||||
|
std::string* result) { |
||||||
|
// Extract the string data representing the list.
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Handle REDIS negative indices (from the end); fast iff Length() takes O(1)
|
||||||
|
if (index < 0) { |
||||||
|
index = Length(key) - (-index); //replace (-i) with (N-i).
|
||||||
|
} |
||||||
|
|
||||||
|
// Iterate through the list until the desired index is found.
|
||||||
|
int curIndex = 0; |
||||||
|
RedisListIterator it(data); |
||||||
|
while(curIndex < index && !it.Done()) { |
||||||
|
++curIndex; |
||||||
|
it.Skip(); |
||||||
|
} |
||||||
|
|
||||||
|
// If we actually found the index
|
||||||
|
if (curIndex == index && !it.Done()) { |
||||||
|
Slice elem; |
||||||
|
it.GetCurrent(&elem); |
||||||
|
if (result != NULL) { |
||||||
|
*result = elem.ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Return a truncated version of the list.
|
||||||
|
// First, negative values for first/last are interpreted as "end of list".
|
||||||
|
// So, if first == -1, then it is re-set to index: (Length(key) - 1)
|
||||||
|
// Then, return exactly those indices i such that first <= i <= last.
|
||||||
|
// : throws RedisListException
|
||||||
|
std::vector<std::string> RedisLists::Range(const std::string& key, |
||||||
|
int32_t first, int32_t last) { |
||||||
|
// Extract the string data representing the list.
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Handle negative bounds (-1 means last element, etc.)
|
||||||
|
int listLen = Length(key); |
||||||
|
if (first < 0) { |
||||||
|
first = listLen - (-first); // Replace (-x) with (N-x)
|
||||||
|
} |
||||||
|
if (last < 0) { |
||||||
|
last = listLen - (-last); |
||||||
|
} |
||||||
|
|
||||||
|
// Verify bounds (and truncate the range so that it is valid)
|
||||||
|
first = std::max(first, 0); |
||||||
|
last = std::min(last, listLen-1); |
||||||
|
int len = std::max(last-first+1, 0); |
||||||
|
|
||||||
|
// Initialize the resulting list
|
||||||
|
std::vector<std::string> result(len); |
||||||
|
|
||||||
|
// Traverse the list and update the vector
|
||||||
|
int curIdx = 0; |
||||||
|
Slice elem; |
||||||
|
for (RedisListIterator it(data); !it.Done() && curIdx<=last; it.Skip()) { |
||||||
|
if (first <= curIdx && curIdx <= last) { |
||||||
|
it.GetCurrent(&elem); |
||||||
|
result[curIdx-first].assign(elem.data(),elem.size()); |
||||||
|
} |
||||||
|
|
||||||
|
++curIdx; |
||||||
|
} |
||||||
|
|
||||||
|
// Return the result. Might be empty
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// Print the (list: key) out to stdout. For debugging mostly. Public for now.
|
||||||
|
void RedisLists::Print(const std::string& key) { |
||||||
|
// Extract the string data representing the list.
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Iterate through the list and print the items
|
||||||
|
Slice elem; |
||||||
|
for (RedisListIterator it(data); !it.Done(); it.Skip()) { |
||||||
|
it.GetCurrent(&elem); |
||||||
|
std::cout << "ITEM " << elem.ToString() << std::endl; |
||||||
|
} |
||||||
|
|
||||||
|
//Now print the byte data
|
||||||
|
RedisListIterator it(data); |
||||||
|
std::cout << "==Printing data==" << std::endl; |
||||||
|
std::cout << data.size() << std::endl; |
||||||
|
std::cout << it.Size() << " " << it.Length() << std::endl; |
||||||
|
Slice result = it.WriteResult(); |
||||||
|
std::cout << result.data() << std::endl; |
||||||
|
if (true) { |
||||||
|
std::cout << "size: " << result.size() << std::endl; |
||||||
|
const char* val = result.data(); |
||||||
|
for(int i=0; i<(int)result.size(); ++i) { |
||||||
|
std::cout << (int)val[i] << " " << (val[i]>=32?val[i]:' ') << std::endl; |
||||||
|
} |
||||||
|
std::cout << std::endl; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Insert/Update Functions
|
||||||
|
/// Note: The "real" insert function is private. See below.
|
||||||
|
|
||||||
|
// InsertBefore and InsertAfter are simply wrappers around the Insert function.
|
||||||
|
int RedisLists::InsertBefore(const std::string& key, const std::string& pivot, |
||||||
|
const std::string& value) { |
||||||
|
return Insert(key, pivot, value, false); |
||||||
|
} |
||||||
|
|
||||||
|
int RedisLists::InsertAfter(const std::string& key, const std::string& pivot, |
||||||
|
const std::string& value) { |
||||||
|
return Insert(key, pivot, value, true); |
||||||
|
} |
||||||
|
|
||||||
|
// Prepend value onto beginning of (list: key)
|
||||||
|
// : throws RedisListException
|
||||||
|
int RedisLists::PushLeft(const std::string& key, const std::string& value) { |
||||||
|
// Get the original list data
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Construct the result
|
||||||
|
RedisListIterator it(data); |
||||||
|
it.Reserve(it.Size() + it.SizeOf(value)); |
||||||
|
it.InsertElement(value); |
||||||
|
|
||||||
|
// Push the data back to the db and return the length
|
||||||
|
db_->Put(put_option_, key, it.WriteResult()); |
||||||
|
return it.Length(); |
||||||
|
} |
||||||
|
|
||||||
|
// Append value onto end of (list: key)
|
||||||
|
// TODO: Make this O(1) time. Might require MergeOperator.
|
||||||
|
// : throws RedisListException
|
||||||
|
int RedisLists::PushRight(const std::string& key, const std::string& value) { |
||||||
|
// Get the original list data
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Create an iterator to the data and seek to the end.
|
||||||
|
RedisListIterator it(data); |
||||||
|
it.Reserve(it.Size() + it.SizeOf(value)); |
||||||
|
while (!it.Done()) { |
||||||
|
it.Push(); // Write each element as we go
|
||||||
|
} |
||||||
|
|
||||||
|
// Insert the new element at the current position (the end)
|
||||||
|
it.InsertElement(value); |
||||||
|
|
||||||
|
// Push it back to the db, and return length
|
||||||
|
db_->Put(put_option_, key, it.WriteResult()); |
||||||
|
return it.Length(); |
||||||
|
} |
||||||
|
|
||||||
|
// Set (list: key)[idx] = val. Return true on success, false on fail.
|
||||||
|
// : throws RedisListException
|
||||||
|
bool RedisLists::Set(const std::string& key, int32_t index, |
||||||
|
const std::string& value) { |
||||||
|
// Get the original list data
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Handle negative index for REDIS (meaning -index from end of list)
|
||||||
|
if (index < 0) { |
||||||
|
index = Length(key) - (-index); |
||||||
|
} |
||||||
|
|
||||||
|
// Iterate through the list until we find the element we want
|
||||||
|
int curIndex = 0; |
||||||
|
RedisListIterator it(data); |
||||||
|
it.Reserve(it.Size() + it.SizeOf(value)); // Over-estimate is fine
|
||||||
|
while(curIndex < index && !it.Done()) { |
||||||
|
it.Push(); |
||||||
|
++curIndex; |
||||||
|
} |
||||||
|
|
||||||
|
// If not found, return false (this occurs when index was invalid)
|
||||||
|
if (it.Done() || curIndex != index) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// Write the new element value, and drop the previous element value
|
||||||
|
it.InsertElement(value); |
||||||
|
it.Skip(); |
||||||
|
|
||||||
|
// Write the data to the database
|
||||||
|
// Check status, since it needs to return true/false guarantee
|
||||||
|
Status s = db_->Put(put_option_, key, it.WriteResult()); |
||||||
|
|
||||||
|
// Success
|
||||||
|
return s.ok(); |
||||||
|
} |
||||||
|
|
||||||
|
/// Delete / Remove / Pop functions
|
||||||
|
|
||||||
|
// Trim (list: key) so that it will only contain the indices from start..stop
|
||||||
|
// Invalid indices will not generate an error, just empty,
|
||||||
|
// or the portion of the list that fits in this interval
|
||||||
|
// : throws RedisListException
|
||||||
|
bool RedisLists::Trim(const std::string& key, int32_t start, int32_t stop) { |
||||||
|
// Get the original list data
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Handle negative indices in REDIS
|
||||||
|
int listLen = Length(key); |
||||||
|
if (start < 0) { |
||||||
|
start = listLen - (-start); |
||||||
|
} |
||||||
|
if (stop < 0) { |
||||||
|
stop = listLen - (-stop); |
||||||
|
} |
||||||
|
|
||||||
|
// Truncate bounds to only fit in the list
|
||||||
|
start = std::max(start, 0); |
||||||
|
stop = std::min(stop, listLen-1); |
||||||
|
|
||||||
|
// Construct an iterator for the list. Drop all undesired elements.
|
||||||
|
int curIndex = 0; |
||||||
|
RedisListIterator it(data); |
||||||
|
it.Reserve(it.Size()); // Over-estimate
|
||||||
|
while(!it.Done()) { |
||||||
|
// If not within the range, just skip the item (drop it).
|
||||||
|
// Otherwise, continue as usual.
|
||||||
|
if (start <= curIndex && curIndex <= stop) { |
||||||
|
it.Push(); |
||||||
|
} else { |
||||||
|
it.Skip(); |
||||||
|
} |
||||||
|
|
||||||
|
// Increment the current index
|
||||||
|
++curIndex; |
||||||
|
} |
||||||
|
|
||||||
|
// Write the (possibly empty) result to the database
|
||||||
|
Status s = db_->Put(put_option_, key, it.WriteResult()); |
||||||
|
|
||||||
|
// Return true as long as the write succeeded
|
||||||
|
return s.ok(); |
||||||
|
} |
||||||
|
|
||||||
|
// Return and remove the first element in the list (or "" if empty)
|
||||||
|
// : throws RedisListException
|
||||||
|
bool RedisLists::PopLeft(const std::string& key, std::string* result) { |
||||||
|
// Get the original list data
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Point to first element in the list (if it exists), and get its value/size
|
||||||
|
RedisListIterator it(data); |
||||||
|
if (it.Length() > 0) { // Proceed only if list is non-empty
|
||||||
|
Slice elem; |
||||||
|
it.GetCurrent(&elem); // Store the value of the first element
|
||||||
|
it.Reserve(it.Size() - it.SizeOf(elem)); |
||||||
|
it.Skip(); // DROP the first item and move to next
|
||||||
|
|
||||||
|
// Update the db
|
||||||
|
db_->Put(put_option_, key, it.WriteResult()); |
||||||
|
|
||||||
|
// Return the value
|
||||||
|
if (result != NULL) { |
||||||
|
*result = elem.ToString(); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Remove and return the last element in the list (or "" if empty)
|
||||||
|
// TODO: Make this O(1). Might require MergeOperator.
|
||||||
|
// : throws RedisListException
|
||||||
|
bool RedisLists::PopRight(const std::string& key, std::string* result) { |
||||||
|
// Extract the original list data
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Construct an iterator to the data and move to last element
|
||||||
|
RedisListIterator it(data); |
||||||
|
it.Reserve(it.Size()); |
||||||
|
int len = it.Length(); |
||||||
|
int curIndex = 0; |
||||||
|
while(curIndex < (len-1) && !it.Done()) { |
||||||
|
it.Push(); |
||||||
|
++curIndex; |
||||||
|
} |
||||||
|
|
||||||
|
// Extract and drop/skip the last element
|
||||||
|
if (curIndex == len-1) { |
||||||
|
assert(!it.Done()); // Sanity check. Should not have ended here.
|
||||||
|
|
||||||
|
// Extract and pop the element
|
||||||
|
Slice elem; |
||||||
|
it.GetCurrent(&elem); // Save value of element.
|
||||||
|
it.Skip(); // Skip the element
|
||||||
|
|
||||||
|
// Write the result to the database
|
||||||
|
db_->Put(put_option_, key, it.WriteResult()); |
||||||
|
|
||||||
|
// Return the value
|
||||||
|
if (result != NULL) { |
||||||
|
*result = elem.ToString(); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
// Must have been an empty list
|
||||||
|
assert(it.Done() && len==0 && curIndex == 0); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Remove the (first or last) "num" occurrences of value in (list: key)
|
||||||
|
// : throws RedisListException
|
||||||
|
int RedisLists::Remove(const std::string& key, int32_t num, |
||||||
|
const std::string& value) { |
||||||
|
// Negative num ==> RemoveLast; Positive num ==> Remove First
|
||||||
|
if (num < 0) { |
||||||
|
return RemoveLast(key, -num, value); |
||||||
|
} else if (num > 0) { |
||||||
|
return RemoveFirst(key, num, value); |
||||||
|
} else { |
||||||
|
return RemoveFirst(key, Length(key), value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Remove the first "num" occurrences of value in (list: key).
|
||||||
|
// : throws RedisListException
|
||||||
|
int RedisLists::RemoveFirst(const std::string& key, int32_t num, |
||||||
|
const std::string& value) { |
||||||
|
// Ensure that the number is positive
|
||||||
|
assert(num >= 0); |
||||||
|
|
||||||
|
// Extract the original list data
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Traverse the list, appending all but the desired occurrences of value
|
||||||
|
int numSkipped = 0; // Keep track of the number of times value is seen
|
||||||
|
Slice elem; |
||||||
|
RedisListIterator it(data); |
||||||
|
it.Reserve(it.Size()); |
||||||
|
while (!it.Done()) { |
||||||
|
it.GetCurrent(&elem); |
||||||
|
|
||||||
|
if (elem == value && numSkipped < num) { |
||||||
|
// Drop this item if desired
|
||||||
|
it.Skip(); |
||||||
|
++numSkipped; |
||||||
|
} else { |
||||||
|
// Otherwise keep the item and proceed as normal
|
||||||
|
it.Push(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Put the result back to the database
|
||||||
|
db_->Put(put_option_, key, it.WriteResult()); |
||||||
|
|
||||||
|
// Return the number of elements removed
|
||||||
|
return numSkipped; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Remove the last "num" occurrences of value in (list: key).
|
||||||
|
// TODO: I traverse the list 2x. Make faster. Might require MergeOperator.
|
||||||
|
// : throws RedisListException
|
||||||
|
int RedisLists::RemoveLast(const std::string& key, int32_t num, |
||||||
|
const std::string& value) { |
||||||
|
// Ensure that the number is positive
|
||||||
|
assert(num >= 0); |
||||||
|
|
||||||
|
// Extract the original list data
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Temporary variable to hold the "current element" in the blocks below
|
||||||
|
Slice elem; |
||||||
|
|
||||||
|
// Count the total number of occurrences of value
|
||||||
|
int totalOccs = 0; |
||||||
|
for (RedisListIterator it(data); !it.Done(); it.Skip()) { |
||||||
|
it.GetCurrent(&elem); |
||||||
|
if (elem == value) { |
||||||
|
++totalOccs; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Construct an iterator to the data. Reserve enough space for the result.
|
||||||
|
RedisListIterator it(data); |
||||||
|
int bytesRemoved = std::min(num,totalOccs)*it.SizeOf(value); |
||||||
|
it.Reserve(it.Size() - bytesRemoved); |
||||||
|
|
||||||
|
// Traverse the list, appending all but the desired occurrences of value.
|
||||||
|
// Note: "Drop the last k occurrences" is equivalent to
|
||||||
|
// "keep only the first n-k occurrences", where n is total occurrences.
|
||||||
|
int numKept = 0; // Keep track of the number of times value is kept
|
||||||
|
while(!it.Done()) { |
||||||
|
it.GetCurrent(&elem); |
||||||
|
|
||||||
|
// If we are within the deletion range and equal to value, drop it.
|
||||||
|
// Otherwise, append/keep/push it.
|
||||||
|
if (elem == value) { |
||||||
|
if (numKept < totalOccs - num) { |
||||||
|
it.Push(); |
||||||
|
++numKept; |
||||||
|
} else { |
||||||
|
it.Skip(); |
||||||
|
} |
||||||
|
} else { |
||||||
|
// Always append the others
|
||||||
|
it.Push(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Put the result back to the database
|
||||||
|
db_->Put(put_option_, key, it.WriteResult()); |
||||||
|
|
||||||
|
// Return the number of elements removed
|
||||||
|
return totalOccs - numKept; |
||||||
|
} |
||||||
|
|
||||||
|
/// Private functions
|
||||||
|
|
||||||
|
// Insert element value into (list: key), right before/after
|
||||||
|
// the first occurrence of pivot
|
||||||
|
// : throws RedisListException
|
||||||
|
int RedisLists::Insert(const std::string& key, const std::string& pivot, |
||||||
|
const std::string& value, bool insert_after) { |
||||||
|
// Get the original list data
|
||||||
|
std::string data; |
||||||
|
db_->Get(get_option_, key, &data); |
||||||
|
|
||||||
|
// Construct an iterator to the data and reserve enough space for result.
|
||||||
|
RedisListIterator it(data); |
||||||
|
it.Reserve(it.Size() + it.SizeOf(value)); |
||||||
|
|
||||||
|
// Iterate through the list until we find the element we want
|
||||||
|
Slice elem; |
||||||
|
bool found = false; |
||||||
|
while(!it.Done() && !found) { |
||||||
|
it.GetCurrent(&elem); |
||||||
|
|
||||||
|
// When we find the element, insert the element and mark found
|
||||||
|
if (elem == pivot) { // Found it!
|
||||||
|
found = true; |
||||||
|
if (insert_after == true) { // Skip one more, if inserting after it
|
||||||
|
it.Push(); |
||||||
|
} |
||||||
|
it.InsertElement(value); |
||||||
|
} else { |
||||||
|
it.Push(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// Put the data (string) into the database
|
||||||
|
if (found) { |
||||||
|
db_->Put(put_option_, key, it.WriteResult()); |
||||||
|
} |
||||||
|
|
||||||
|
// Returns the new (possibly unchanged) length of the list
|
||||||
|
return it.Length(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,104 @@ |
|||||||
|
/**
|
||||||
|
* A (persistent) Redis API built using the rocksdb backend. |
||||||
|
* Implements Redis Lists as described on: http://redis.io/commands#list
|
||||||
|
* |
||||||
|
* @throws All functions may throw a RedisListException |
||||||
|
* |
||||||
|
* @author Deon Nicholas (dnicholas@fb.com) |
||||||
|
* Copyright 2013 Facebook |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <string> |
||||||
|
#include "leveldb/db.h" |
||||||
|
#include "redis_list_iterator.h" |
||||||
|
#include "redis_list_exception.h" |
||||||
|
|
||||||
|
namespace leveldb { |
||||||
|
|
||||||
|
/// The Redis functionality (see http://redis.io/commands#list)
|
||||||
|
/// All functions may THROW a RedisListException
|
||||||
|
class RedisLists { |
||||||
|
public: // Constructors / Destructors
|
||||||
|
/// Construct a new RedisLists database, with name/path of db.
|
||||||
|
/// Will clear the database on open iff destructive is true (default false).
|
||||||
|
/// Otherwise, it will restore saved changes.
|
||||||
|
/// May throw RedisListException
|
||||||
|
RedisLists(const std::string& db_path, |
||||||
|
Options options, bool destructive = false); |
||||||
|
|
||||||
|
public: // Accessors
|
||||||
|
/// The number of items in (list: key)
|
||||||
|
int Length(const std::string& key); |
||||||
|
|
||||||
|
/// Search the list for the (index)'th item (0-based) in (list:key)
|
||||||
|
/// A negative index indicates: "from end-of-list"
|
||||||
|
/// If index is within range: return true, and return the value in *result.
|
||||||
|
/// If (index < -length OR index>=length), then index is out of range:
|
||||||
|
/// return false (and *result is left unchanged)
|
||||||
|
/// May throw RedisListException
|
||||||
|
bool Index(const std::string& key, int32_t index, |
||||||
|
std::string* result); |
||||||
|
|
||||||
|
/// Return (list: key)[first..last] (inclusive)
|
||||||
|
/// May throw RedisListException
|
||||||
|
std::vector<std::string> Range(const std::string& key, |
||||||
|
int32_t first, int32_t last); |
||||||
|
|
||||||
|
/// Prints the entire (list: key), for debugging.
|
||||||
|
void Print(const std::string& key); |
||||||
|
|
||||||
|
public: // Insert/Update
|
||||||
|
/// Insert value before/after pivot in (list: key). Return the length.
|
||||||
|
/// May throw RedisListException
|
||||||
|
int InsertBefore(const std::string& key, const std::string& pivot, |
||||||
|
const std::string& value); |
||||||
|
int InsertAfter(const std::string& key, const std::string& pivot, |
||||||
|
const std::string& value); |
||||||
|
|
||||||
|
/// Push / Insert value at beginning/end of the list. Return the length.
|
||||||
|
/// May throw RedisListException
|
||||||
|
int PushLeft(const std::string& key, const std::string& value); |
||||||
|
int PushRight(const std::string& key, const std::string& value); |
||||||
|
|
||||||
|
/// Set (list: key)[idx] = val. Return true on success, false on fail
|
||||||
|
/// May throw RedisListException
|
||||||
|
bool Set(const std::string& key, int32_t index, const std::string& value); |
||||||
|
|
||||||
|
public: // Delete / Remove / Pop / Trim
|
||||||
|
/// Trim (list: key) so that it will only contain the indices from start..stop
|
||||||
|
/// Returns true on success
|
||||||
|
/// May throw RedisListException
|
||||||
|
bool Trim(const std::string& key, int32_t start, int32_t stop); |
||||||
|
|
||||||
|
/// If list is empty, return false and leave *result unchanged.
|
||||||
|
/// Else, remove the first/last elem, store it in *result, and return true
|
||||||
|
bool PopLeft(const std::string& key, std::string* result); // First
|
||||||
|
bool PopRight(const std::string& key, std::string* result); // Last
|
||||||
|
|
||||||
|
/// Remove the first (or last) num occurrences of value from the list (key)
|
||||||
|
/// Return the number of elements removed.
|
||||||
|
/// May throw RedisListException
|
||||||
|
int Remove(const std::string& key, int32_t num, |
||||||
|
const std::string& value); |
||||||
|
int RemoveFirst(const std::string& key, int32_t num, |
||||||
|
const std::string& value); |
||||||
|
int RemoveLast(const std::string& key, int32_t num, |
||||||
|
const std::string& value); |
||||||
|
|
||||||
|
private: // Private Functions
|
||||||
|
/// Calls InsertBefore or InsertAfter
|
||||||
|
int Insert(const std::string& key, const std::string& pivot, |
||||||
|
const std::string& value, bool insert_after); |
||||||
|
private: |
||||||
|
std::string db_name_; // The actual database name/path
|
||||||
|
WriteOptions put_option_; |
||||||
|
ReadOptions get_option_; |
||||||
|
|
||||||
|
/// The backend rocksdb database.
|
||||||
|
/// Map : key --> list
|
||||||
|
/// where a list is a sequence of elements
|
||||||
|
/// and an element is a 4-byte integer (n), followed by n bytes of data
|
||||||
|
std::unique_ptr<DB> db_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace leveldb
|
@ -0,0 +1,900 @@ |
|||||||
|
/**
|
||||||
|
* A test harness for the Redis API built on rocksdb. |
||||||
|
* |
||||||
|
* USAGE: Build with: "make redis_test" (in rocksdb directory). |
||||||
|
* Run unit tests with: "./redis_test" |
||||||
|
* Manual/Interactive user testing: "./redis_test -m" |
||||||
|
* Manual user testing + restart database: "./redis_test -m -d" |
||||||
|
* |
||||||
|
* TODO: Add LARGE random test cases to verify efficiency and scalability |
||||||
|
* |
||||||
|
* @author Deon Nicholas (dnicholas@fb.com) |
||||||
|
* Copyright 2013 Facebook |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
#include <iostream> |
||||||
|
#include <cctype> |
||||||
|
|
||||||
|
#include "redis_lists.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/random.h" |
||||||
|
|
||||||
|
using namespace leveldb; |
||||||
|
using namespace std; |
||||||
|
|
||||||
|
namespace leveldb { |
||||||
|
|
||||||
|
class RedisListsTest { |
||||||
|
public: |
||||||
|
static const string kDefaultDbName; |
||||||
|
static Options options; |
||||||
|
|
||||||
|
RedisListsTest() { |
||||||
|
options.create_if_missing = true; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const string RedisListsTest::kDefaultDbName = "/tmp/redisdefaultdb/"; |
||||||
|
Options RedisListsTest::options = Options(); |
||||||
|
|
||||||
|
// operator== and operator<< are defined below for vectors (lists)
|
||||||
|
// Needed for ASSERT_EQ
|
||||||
|
|
||||||
|
// Compare two lists for equality.
|
||||||
|
bool operator==(const std::vector<std::string>& a, |
||||||
|
const std::vector<std::string>& b) { |
||||||
|
if (a.size() != b.size()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
int n = a.size(); |
||||||
|
for (int i=0; i<n; ++i) { |
||||||
|
if (a[i] != b[i]) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// Print out a list
|
||||||
|
ostream& operator<<(ostream& out, const std::vector<std::string>& vec) { |
||||||
|
out << "["; |
||||||
|
int n = vec.size(); |
||||||
|
for(int i=0; i<n; ++i) { |
||||||
|
if (i > 0) { |
||||||
|
out << ", "; |
||||||
|
} |
||||||
|
out << vec[i]; |
||||||
|
} |
||||||
|
out << "]"; |
||||||
|
return out; |
||||||
|
} |
||||||
|
|
||||||
|
/// THE TEST CASES BEGIN HERE
|
||||||
|
|
||||||
|
// PushRight, Length, Index, Range
|
||||||
|
TEST(RedisListsTest, SimpleTest) { |
||||||
|
RedisLists redis(kDefaultDbName, options, true); // Destructive
|
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// Simple PushRight (should return the new length each time)
|
||||||
|
ASSERT_EQ(redis.PushRight("k1", "v1"), 1); |
||||||
|
ASSERT_EQ(redis.PushRight("k1", "v2"), 2); |
||||||
|
ASSERT_EQ(redis.PushRight("k1", "v3"), 3); |
||||||
|
|
||||||
|
// Check Length and Index() functions
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 3); // Check length
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v1"); // Check valid indices
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v2"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v3"); |
||||||
|
|
||||||
|
// Check range function and vectors
|
||||||
|
std::vector<std::string> result = redis.Range("k1", 0, 2); // Get the list
|
||||||
|
std::vector<std::string> expected_result(3); |
||||||
|
expected_result[0] = "v1"; |
||||||
|
expected_result[1] = "v2"; |
||||||
|
expected_result[2] = "v3"; |
||||||
|
ASSERT_EQ(result, expected_result); // Uses my overloaded operator==() above
|
||||||
|
} |
||||||
|
|
||||||
|
// PushLeft, Length, Index, Range
|
||||||
|
TEST(RedisListsTest, SimpleTest2) { |
||||||
|
RedisLists redis(kDefaultDbName, options, true); // Destructive
|
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// Simple PushRight
|
||||||
|
ASSERT_EQ(redis.PushLeft("k1", "v3"), 1); |
||||||
|
ASSERT_EQ(redis.PushLeft("k1", "v2"), 2); |
||||||
|
ASSERT_EQ(redis.PushLeft("k1", "v1"), 3); |
||||||
|
|
||||||
|
// Check Length and Index() functions
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 3); // Check length
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v1"); // Check valid indices
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v2"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v3"); |
||||||
|
|
||||||
|
// Check range function and vectors
|
||||||
|
std::vector<std::string> result = redis.Range("k1", 0, 2); // Get the list
|
||||||
|
std::vector<std::string> expected_result(3); |
||||||
|
expected_result[0] = "v1"; |
||||||
|
expected_result[1] = "v2"; |
||||||
|
expected_result[2] = "v3"; |
||||||
|
ASSERT_EQ(result, expected_result); // Uses my overloaded operator==() above
|
||||||
|
} |
||||||
|
|
||||||
|
// Exhaustive test of the Index() function
|
||||||
|
TEST(RedisListsTest, IndexTest) { |
||||||
|
RedisLists redis(kDefaultDbName, options, true); // Destructive
|
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// Empty Index check (return empty and should not crash or edit tempv)
|
||||||
|
tempv = "yo"; |
||||||
|
ASSERT_TRUE(!redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "yo"); |
||||||
|
ASSERT_TRUE(!redis.Index("fda", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "yo"); |
||||||
|
ASSERT_TRUE(!redis.Index("random", -12391, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "yo"); |
||||||
|
|
||||||
|
// Simple Pushes (will yield: [v6, v4, v4, v1, v2, v3]
|
||||||
|
redis.PushRight("k1", "v1"); |
||||||
|
redis.PushRight("k1", "v2"); |
||||||
|
redis.PushRight("k1", "v3"); |
||||||
|
redis.PushLeft("k1", "v4"); |
||||||
|
redis.PushLeft("k1", "v4"); |
||||||
|
redis.PushLeft("k1", "v6"); |
||||||
|
|
||||||
|
// Simple, non-negative indices
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v6"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v4"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v4"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v1"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v2"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v3"); |
||||||
|
|
||||||
|
// Negative indices
|
||||||
|
ASSERT_TRUE(redis.Index("k1", -6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v6"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v4"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v4"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v1"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v2"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "v3"); |
||||||
|
|
||||||
|
// Out of bounds (return empty, no crash)
|
||||||
|
ASSERT_TRUE(!redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_TRUE(!redis.Index("k1", 123219, &tempv)); |
||||||
|
ASSERT_TRUE(!redis.Index("k1", -7, &tempv)); |
||||||
|
ASSERT_TRUE(!redis.Index("k1", -129, &tempv)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Exhaustive test of the Range() function
|
||||||
|
TEST(RedisListsTest, RangeTest) { |
||||||
|
RedisLists redis(kDefaultDbName, options, true); // Destructive
|
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// Simple Pushes (will yield: [v6, v4, v4, v1, v2, v3])
|
||||||
|
redis.PushRight("k1", "v1"); |
||||||
|
redis.PushRight("k1", "v2"); |
||||||
|
redis.PushRight("k1", "v3"); |
||||||
|
redis.PushLeft("k1", "v4"); |
||||||
|
redis.PushLeft("k1", "v4"); |
||||||
|
redis.PushLeft("k1", "v6"); |
||||||
|
|
||||||
|
// Sanity check (check the length; make sure it's 6)
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 6); |
||||||
|
|
||||||
|
// Simple range
|
||||||
|
std::vector<std::string> res = redis.Range("k1", 1, 4); |
||||||
|
ASSERT_EQ((int)res.size(), 4); |
||||||
|
ASSERT_EQ(res[0], "v4"); |
||||||
|
ASSERT_EQ(res[1], "v4"); |
||||||
|
ASSERT_EQ(res[2], "v1"); |
||||||
|
ASSERT_EQ(res[3], "v2"); |
||||||
|
|
||||||
|
// Negative indices (i.e.: measured from the end)
|
||||||
|
res = redis.Range("k1", 2, -1); |
||||||
|
ASSERT_EQ((int)res.size(), 4); |
||||||
|
ASSERT_EQ(res[0], "v4"); |
||||||
|
ASSERT_EQ(res[1], "v1"); |
||||||
|
ASSERT_EQ(res[2], "v2"); |
||||||
|
ASSERT_EQ(res[3], "v3"); |
||||||
|
|
||||||
|
res = redis.Range("k1", -6, -4); |
||||||
|
ASSERT_EQ((int)res.size(), 3); |
||||||
|
ASSERT_EQ(res[0], "v6"); |
||||||
|
ASSERT_EQ(res[1], "v4"); |
||||||
|
ASSERT_EQ(res[2], "v4"); |
||||||
|
|
||||||
|
res = redis.Range("k1", -1, 5); |
||||||
|
ASSERT_EQ((int)res.size(), 1); |
||||||
|
ASSERT_EQ(res[0], "v3"); |
||||||
|
|
||||||
|
// Partial / Broken indices
|
||||||
|
res = redis.Range("k1", -3, 1000000); |
||||||
|
ASSERT_EQ((int)res.size(), 3); |
||||||
|
ASSERT_EQ(res[0], "v1"); |
||||||
|
ASSERT_EQ(res[1], "v2"); |
||||||
|
ASSERT_EQ(res[2], "v3"); |
||||||
|
|
||||||
|
res = redis.Range("k1", -1000000, 1); |
||||||
|
ASSERT_EQ((int)res.size(), 2); |
||||||
|
ASSERT_EQ(res[0], "v6"); |
||||||
|
ASSERT_EQ(res[1], "v4"); |
||||||
|
|
||||||
|
// Invalid indices
|
||||||
|
res = redis.Range("k1", 7, 9); |
||||||
|
ASSERT_EQ((int)res.size(), 0); |
||||||
|
|
||||||
|
res = redis.Range("k1", -8, -7); |
||||||
|
ASSERT_EQ((int)res.size(), 0); |
||||||
|
|
||||||
|
res = redis.Range("k1", 3, 2); |
||||||
|
ASSERT_EQ((int)res.size(), 0); |
||||||
|
|
||||||
|
res = redis.Range("k1", 5, -2); |
||||||
|
ASSERT_EQ((int)res.size(), 0); |
||||||
|
|
||||||
|
// Range matches Index
|
||||||
|
res = redis.Range("k1", -6, -4); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, res[0]); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, res[1]); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, res[2]); |
||||||
|
|
||||||
|
// Last check
|
||||||
|
res = redis.Range("k1", 0, -6); |
||||||
|
ASSERT_EQ((int)res.size(), 1); |
||||||
|
ASSERT_EQ(res[0], "v6"); |
||||||
|
} |
||||||
|
|
||||||
|
// Exhaustive test for InsertBefore(), and InsertAfter()
|
||||||
|
TEST(RedisListsTest, InsertTest) { |
||||||
|
RedisLists redis(kDefaultDbName, options, true); |
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// Insert on empty list (return 0, and do not crash)
|
||||||
|
ASSERT_EQ(redis.InsertBefore("k1", "non-exist", "a"), 0); |
||||||
|
ASSERT_EQ(redis.InsertAfter("k1", "other-non-exist", "c"), 0); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 0); |
||||||
|
|
||||||
|
// Push some preliminary stuff [g, f, e, d, c, b, a]
|
||||||
|
redis.PushLeft("k1", "a"); |
||||||
|
redis.PushLeft("k1", "b"); |
||||||
|
redis.PushLeft("k1", "c"); |
||||||
|
redis.PushLeft("k1", "d"); |
||||||
|
redis.PushLeft("k1", "e"); |
||||||
|
redis.PushLeft("k1", "f"); |
||||||
|
redis.PushLeft("k1", "g"); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 7); |
||||||
|
|
||||||
|
// Test InsertBefore
|
||||||
|
int newLength = redis.InsertBefore("k1", "e", "hello"); |
||||||
|
ASSERT_EQ(newLength, 8); |
||||||
|
ASSERT_EQ(redis.Length("k1"), newLength); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "f"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "e"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "hello"); |
||||||
|
|
||||||
|
// Test InsertAfter
|
||||||
|
newLength = redis.InsertAfter("k1", "c", "bye"); |
||||||
|
ASSERT_EQ(newLength, 9); |
||||||
|
ASSERT_EQ(redis.Length("k1"), newLength); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "bye"); |
||||||
|
|
||||||
|
// Test bad value on InsertBefore
|
||||||
|
newLength = redis.InsertBefore("k1", "yo", "x"); |
||||||
|
ASSERT_EQ(newLength, 9); |
||||||
|
ASSERT_EQ(redis.Length("k1"), newLength); |
||||||
|
|
||||||
|
// Test bad value on InsertAfter
|
||||||
|
newLength = redis.InsertAfter("k1", "xxxx", "y"); |
||||||
|
ASSERT_EQ(newLength, 9); |
||||||
|
ASSERT_EQ(redis.Length("k1"), newLength); |
||||||
|
|
||||||
|
// Test InsertBefore beginning
|
||||||
|
newLength = redis.InsertBefore("k1", "g", "begggggggggggggggg"); |
||||||
|
ASSERT_EQ(newLength, 10); |
||||||
|
ASSERT_EQ(redis.Length("k1"), newLength); |
||||||
|
|
||||||
|
// Test InsertAfter end
|
||||||
|
newLength = redis.InsertAfter("k1", "a", "enddd"); |
||||||
|
ASSERT_EQ(newLength, 11); |
||||||
|
ASSERT_EQ(redis.Length("k1"), newLength); |
||||||
|
|
||||||
|
// Make sure nothing weird happened.
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "begggggggggggggggg"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "g"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "f"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "hello"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "e"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "d"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "c"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 7, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "bye"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 8, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "b"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 9, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 10, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "enddd"); |
||||||
|
} |
||||||
|
|
||||||
|
// Exhaustive test of Set function
|
||||||
|
TEST(RedisListsTest, SetTest) { |
||||||
|
RedisLists redis(kDefaultDbName, options, true); |
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// Set on empty list (return false, and do not crash)
|
||||||
|
ASSERT_EQ(redis.Set("k1", 7, "a"), false); |
||||||
|
ASSERT_EQ(redis.Set("k1", 0, "a"), false); |
||||||
|
ASSERT_EQ(redis.Set("k1", -49, "cx"), false); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 0); |
||||||
|
|
||||||
|
// Push some preliminary stuff [g, f, e, d, c, b, a]
|
||||||
|
redis.PushLeft("k1", "a"); |
||||||
|
redis.PushLeft("k1", "b"); |
||||||
|
redis.PushLeft("k1", "c"); |
||||||
|
redis.PushLeft("k1", "d"); |
||||||
|
redis.PushLeft("k1", "e"); |
||||||
|
redis.PushLeft("k1", "f"); |
||||||
|
redis.PushLeft("k1", "g"); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 7); |
||||||
|
|
||||||
|
// Test Regular Set
|
||||||
|
ASSERT_TRUE(redis.Set("k1", 0, "0")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", 3, "3")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", 6, "6")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", 2, "2")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", 5, "5")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", 1, "1")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", 4, "4")); |
||||||
|
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 7); // Size should not change
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "0"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "1"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "2"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "3"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "4"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "5"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "6"); |
||||||
|
|
||||||
|
// Set with negative indices
|
||||||
|
ASSERT_TRUE(redis.Set("k1", -7, "a")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", -4, "d")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", -1, "g")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", -5, "c")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", -2, "f")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", -6, "b")); |
||||||
|
ASSERT_TRUE(redis.Set("k1", -3, "e")); |
||||||
|
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 7); // Size should not change
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "b"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "c"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "d"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "e"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "f"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "g"); |
||||||
|
|
||||||
|
// Bad indices (just out-of-bounds / off-by-one check)
|
||||||
|
ASSERT_EQ(redis.Set("k1", -8, "off-by-one in negative index"), false); |
||||||
|
ASSERT_EQ(redis.Set("k1", 7, "off-by-one-error in positive index"), false); |
||||||
|
ASSERT_EQ(redis.Set("k1", 43892, "big random index should fail"), false); |
||||||
|
ASSERT_EQ(redis.Set("k1", -21391, "large negative index should fail"), false); |
||||||
|
|
||||||
|
// One last check (to make sure nothing weird happened)
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 7); // Size should not change
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "b"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "c"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "d"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "e"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "f"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "g"); |
||||||
|
} |
||||||
|
|
||||||
|
// Testing Insert, Push, and Set, in a mixed environment
|
||||||
|
TEST(RedisListsTest, InsertPushSetTest) { |
||||||
|
RedisLists redis(kDefaultDbName, options, true); // Destructive
|
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// A series of pushes and insertions
|
||||||
|
// Will result in [newbegin, z, a, aftera, x, newend]
|
||||||
|
// Also, check the return value sometimes (should return length)
|
||||||
|
int lengthCheck; |
||||||
|
lengthCheck = redis.PushLeft("k1", "a"); |
||||||
|
ASSERT_EQ(lengthCheck, 1); |
||||||
|
redis.PushLeft("k1", "z"); |
||||||
|
redis.PushRight("k1", "x"); |
||||||
|
lengthCheck = redis.InsertAfter("k1", "a", "aftera"); |
||||||
|
ASSERT_EQ(lengthCheck , 4); |
||||||
|
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore beginning of list
|
||||||
|
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
|
||||||
|
|
||||||
|
// Check
|
||||||
|
std::vector<std::string> res = redis.Range("k1", 0, -1); // Get the list
|
||||||
|
ASSERT_EQ((int)res.size(), 6); |
||||||
|
ASSERT_EQ(res[0], "newbegin"); |
||||||
|
ASSERT_EQ(res[5], "newend"); |
||||||
|
ASSERT_EQ(res[3], "aftera"); |
||||||
|
|
||||||
|
// Testing duplicate values/pivots (multiple occurrences of 'a')
|
||||||
|
ASSERT_TRUE(redis.Set("k1", 0, "a")); // [a, z, a, aftera, x, newend]
|
||||||
|
redis.InsertAfter("k1", "a", "happy"); // [a, happy, z, a, aftera, ...]
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "happy"); |
||||||
|
redis.InsertBefore("k1", "a", "sad"); // [sad, a, happy, z, a, aftera, ...]
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "sad"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "happy"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "aftera"); |
||||||
|
redis.InsertAfter("k1", "a", "zz"); // [sad, a, zz, happy, z, a, aftera, ...]
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "zz"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "aftera"); |
||||||
|
ASSERT_TRUE(redis.Set("k1", 1, "nota")); // [sad, nota, zz, happy, z, a, ...]
|
||||||
|
redis.InsertBefore("k1", "a", "ba"); // [sad, nota, zz, happy, z, ba, a, ...]
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "z"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "ba"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
|
||||||
|
// We currently have: [sad, nota, zz, happy, z, ba, a, aftera, x, newend]
|
||||||
|
// redis.Print("k1"); // manually check
|
||||||
|
|
||||||
|
// Test Inserting before/after non-existent values
|
||||||
|
lengthCheck = redis.Length("k1"); // Ensure that the length doesnt change
|
||||||
|
ASSERT_EQ(lengthCheck, 10); |
||||||
|
ASSERT_EQ(redis.InsertBefore("k1", "non-exist", "randval"), lengthCheck); |
||||||
|
ASSERT_EQ(redis.InsertAfter("k1", "nothing", "a"), lengthCheck); |
||||||
|
ASSERT_EQ(redis.InsertAfter("randKey", "randVal", "ranValue"), 0); // Empty
|
||||||
|
ASSERT_EQ(redis.Length("k1"), lengthCheck); // The length should not change
|
||||||
|
|
||||||
|
// Simply Test the Set() function
|
||||||
|
redis.Set("k1", 5, "ba2"); |
||||||
|
redis.InsertBefore("k1", "ba2", "beforeba2"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "z"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "beforeba2"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "ba2"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 7, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
|
||||||
|
// We have: [sad, nota, zz, happy, z, beforeba2, ba2, a, aftera, x, newend]
|
||||||
|
|
||||||
|
// Set() with negative indices
|
||||||
|
redis.Set("k1", -1, "endprank"); |
||||||
|
ASSERT_TRUE(!redis.Index("k1", 11, &tempv)); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 10, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "endprank"); // Ensure Set worked correctly
|
||||||
|
redis.Set("k1", -11, "t"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "t"); |
||||||
|
|
||||||
|
// Test out of bounds Set
|
||||||
|
ASSERT_EQ(redis.Set("k1", -12, "ssd"), false); |
||||||
|
ASSERT_EQ(redis.Set("k1", 11, "sasd"), false); |
||||||
|
ASSERT_EQ(redis.Set("k1", 1200, "big"), false); |
||||||
|
} |
||||||
|
|
||||||
|
// Testing Trim, Pop
|
||||||
|
TEST(RedisListsTest, TrimPopTest) { |
||||||
|
RedisLists redis(kDefaultDbName, options, true); // Destructive
|
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// A series of pushes and insertions
|
||||||
|
// Will result in [newbegin, z, a, aftera, x, newend]
|
||||||
|
redis.PushLeft("k1", "a"); |
||||||
|
redis.PushLeft("k1", "z"); |
||||||
|
redis.PushRight("k1", "x"); |
||||||
|
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore start of list
|
||||||
|
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
|
||||||
|
redis.InsertAfter("k1", "a", "aftera"); |
||||||
|
|
||||||
|
// Simple PopLeft/Right test
|
||||||
|
ASSERT_TRUE(redis.PopLeft("k1", &tempv)); |
||||||
|
ASSERT_EQ(tempv, "newbegin"); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 5); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "z"); |
||||||
|
ASSERT_TRUE(redis.PopRight("k1", &tempv)); |
||||||
|
ASSERT_EQ(tempv, "newend"); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 4); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "x"); |
||||||
|
|
||||||
|
// Now have: [z, a, aftera, x]
|
||||||
|
|
||||||
|
// Test Trim
|
||||||
|
ASSERT_TRUE(redis.Trim("k1", 0, -1)); // [z, a, aftera, x] (do nothing)
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 4); |
||||||
|
ASSERT_TRUE(redis.Trim("k1", 0, 2)); // [z, a, aftera]
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 3); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "aftera"); |
||||||
|
ASSERT_TRUE(redis.Trim("k1", 1, 1)); // [a]
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 1); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
|
||||||
|
// Test out of bounds (empty) trim
|
||||||
|
ASSERT_TRUE(redis.Trim("k1", 1, 0)); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 0); |
||||||
|
|
||||||
|
// Popping with empty list (return empty without error)
|
||||||
|
ASSERT_TRUE(!redis.PopLeft("k1", &tempv)); |
||||||
|
ASSERT_TRUE(!redis.PopRight("k1", &tempv)); |
||||||
|
ASSERT_TRUE(redis.Trim("k1", 0, 5)); |
||||||
|
|
||||||
|
// Exhaustive Trim test (negative and invalid indices)
|
||||||
|
// Will start in [newbegin, z, a, aftera, x, newend]
|
||||||
|
redis.PushLeft("k1", "a"); |
||||||
|
redis.PushLeft("k1", "z"); |
||||||
|
redis.PushRight("k1", "x"); |
||||||
|
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore start of list
|
||||||
|
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
|
||||||
|
redis.InsertAfter("k1", "a", "aftera"); |
||||||
|
ASSERT_TRUE(redis.Trim("k1", -6, -1)); // Should do nothing
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 6); |
||||||
|
ASSERT_TRUE(redis.Trim("k1", 1, -2)); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "z"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "x"); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 4); |
||||||
|
ASSERT_TRUE(redis.Trim("k1", -3, -2)); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 2); |
||||||
|
} |
||||||
|
|
||||||
|
// Testing Remove, RemoveFirst, RemoveLast
|
||||||
|
TEST(RedisListsTest, RemoveTest) { |
||||||
|
RedisLists redis(kDefaultDbName, options, true); // Destructive
|
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// A series of pushes and insertions
|
||||||
|
// Will result in [newbegin, z, a, aftera, x, newend, a, a]
|
||||||
|
redis.PushLeft("k1", "a"); |
||||||
|
redis.PushLeft("k1", "z"); |
||||||
|
redis.PushRight("k1", "x"); |
||||||
|
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore start of list
|
||||||
|
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
|
||||||
|
redis.InsertAfter("k1", "a", "aftera"); |
||||||
|
redis.PushRight("k1", "a"); |
||||||
|
redis.PushRight("k1", "a"); |
||||||
|
|
||||||
|
// Verify
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "newbegin"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
|
||||||
|
// Check RemoveFirst (Remove the first two 'a')
|
||||||
|
// Results in [newbegin, z, aftera, x, newend, a]
|
||||||
|
int numRemoved = redis.Remove("k1", 2, "a"); |
||||||
|
ASSERT_EQ(numRemoved, 2); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "newbegin"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "z"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "newend"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 5, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
ASSERT_EQ(redis.Length("k1"), 6); |
||||||
|
|
||||||
|
// Repopulate some stuff
|
||||||
|
// Results in: [x, x, x, x, x, newbegin, z, x, aftera, x, newend, a, x]
|
||||||
|
redis.PushLeft("k1", "x"); |
||||||
|
redis.PushLeft("k1", "x"); |
||||||
|
redis.PushLeft("k1", "x"); |
||||||
|
redis.PushLeft("k1", "x"); |
||||||
|
redis.PushLeft("k1", "x"); |
||||||
|
redis.PushRight("k1", "x"); |
||||||
|
redis.InsertAfter("k1", "z", "x"); |
||||||
|
|
||||||
|
// Test removal from end
|
||||||
|
numRemoved = redis.Remove("k1", -2, "x"); |
||||||
|
ASSERT_EQ(numRemoved, 2); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 8, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "aftera"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 9, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "newend"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 10, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
ASSERT_TRUE(!redis.Index("k1", 11, &tempv)); |
||||||
|
numRemoved = redis.Remove("k1", -2, "x"); |
||||||
|
ASSERT_EQ(numRemoved, 2); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 4, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "newbegin"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 6, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "aftera"); |
||||||
|
|
||||||
|
// We now have: [x, x, x, x, newbegin, z, aftera, newend, a]
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 9); |
||||||
|
ASSERT_TRUE(redis.Index("k1", -1, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "a"); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "x"); |
||||||
|
|
||||||
|
// Test over-shooting (removing more than there exists)
|
||||||
|
numRemoved = redis.Remove("k1", -9000, "x"); |
||||||
|
ASSERT_EQ(numRemoved , 4); // Only really removed 4
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 5); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "newbegin"); |
||||||
|
numRemoved = redis.Remove("k1", 1, "x"); |
||||||
|
ASSERT_EQ(numRemoved, 0); |
||||||
|
|
||||||
|
// Try removing ALL!
|
||||||
|
numRemoved = redis.Remove("k1", 0, "newbegin"); // REMOVE 0 will remove all!
|
||||||
|
ASSERT_EQ(numRemoved, 1); |
||||||
|
|
||||||
|
// Removal from an empty-list
|
||||||
|
ASSERT_TRUE(redis.Trim("k1", 1, 0)); |
||||||
|
numRemoved = redis.Remove("k1", 1, "z"); |
||||||
|
ASSERT_EQ(numRemoved, 0); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Test Multiple keys and Persistence
|
||||||
|
TEST(RedisListsTest, PersistenceMultiKeyTest) { |
||||||
|
|
||||||
|
string tempv; // Used below for all Index(), PopRight(), PopLeft()
|
||||||
|
|
||||||
|
// Block one: populate a single key in the database
|
||||||
|
{ |
||||||
|
RedisLists redis(kDefaultDbName, options, true); // Destructive
|
||||||
|
|
||||||
|
// A series of pushes and insertions
|
||||||
|
// Will result in [newbegin, z, a, aftera, x, newend, a, a]
|
||||||
|
redis.PushLeft("k1", "a"); |
||||||
|
redis.PushLeft("k1", "z"); |
||||||
|
redis.PushRight("k1", "x"); |
||||||
|
redis.InsertBefore("k1", "z", "newbegin"); // InsertBefore start of list
|
||||||
|
redis.InsertAfter("k1", "x", "newend"); // InsertAfter end of list
|
||||||
|
redis.InsertAfter("k1", "a", "aftera"); |
||||||
|
redis.PushRight("k1", "a"); |
||||||
|
redis.PushRight("k1", "a"); |
||||||
|
|
||||||
|
ASSERT_TRUE(redis.Index("k1", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "aftera"); |
||||||
|
} |
||||||
|
|
||||||
|
// Block two: make sure changes were saved and add some other key
|
||||||
|
{ |
||||||
|
RedisLists redis(kDefaultDbName, options, false); // Persistent, non-destructive
|
||||||
|
|
||||||
|
// Check
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 8); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 3, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "aftera"); |
||||||
|
|
||||||
|
redis.PushRight("k2", "randomkey"); |
||||||
|
redis.PushLeft("k2", "sas"); |
||||||
|
|
||||||
|
redis.PopLeft("k1", &tempv); |
||||||
|
} |
||||||
|
|
||||||
|
// Block three: Verify the changes from block 2
|
||||||
|
{ |
||||||
|
RedisLists redis(kDefaultDbName, options, false); // Presistent, non-destructive
|
||||||
|
|
||||||
|
// Check
|
||||||
|
ASSERT_EQ(redis.Length("k1"), 7); |
||||||
|
ASSERT_EQ(redis.Length("k2"), 2); |
||||||
|
ASSERT_TRUE(redis.Index("k1", 0, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "z"); |
||||||
|
ASSERT_TRUE(redis.Index("k2", -2, &tempv)); |
||||||
|
ASSERT_EQ(tempv, "sas"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// THE manual REDIS TEST begins here
|
||||||
|
/// THIS WILL ONLY OCCUR IF YOU RUN: ./redis_test -m
|
||||||
|
|
||||||
|
void MakeUpper(std::string* const s) { |
||||||
|
int len = s->length(); |
||||||
|
for(int i=0; i<len; ++i) { |
||||||
|
(*s)[i] = toupper((*s)[i]); // C-version defined in <ctype.h>
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Allows the user to enter in REDIS commands into the command-line.
|
||||||
|
/// This is useful for manual / interacticve testing / debugging.
|
||||||
|
/// Use destructive=true to clean the database before use.
|
||||||
|
/// Use destructive=false to remember the previous state (i.e.: persistent)
|
||||||
|
/// Should be called from main function.
|
||||||
|
int manual_redis_test(bool destructive){ |
||||||
|
RedisLists redis(RedisListsTest::kDefaultDbName, |
||||||
|
RedisListsTest::options, |
||||||
|
destructive); |
||||||
|
|
||||||
|
// TODO: Right now, please use spaces to separate each word.
|
||||||
|
// In actual redis, you can use quotes to specify compound values
|
||||||
|
// Example: RPUSH mylist "this is a compound value"
|
||||||
|
|
||||||
|
std::string command; |
||||||
|
while(true) { |
||||||
|
cin >> command; |
||||||
|
MakeUpper(&command); |
||||||
|
|
||||||
|
if (command == "LINSERT") { |
||||||
|
std::string k, t, p, v; |
||||||
|
cin >> k >> t >> p >> v; |
||||||
|
MakeUpper(&t); |
||||||
|
if (t=="BEFORE") { |
||||||
|
std::cout << redis.InsertBefore(k, p, v) << std::endl; |
||||||
|
} else if (t=="AFTER") { |
||||||
|
std::cout << redis.InsertAfter(k, p, v) << std::endl; |
||||||
|
} |
||||||
|
} else if (command == "LPUSH") { |
||||||
|
std::string k, v; |
||||||
|
std::cin >> k >> v; |
||||||
|
redis.PushLeft(k, v); |
||||||
|
} else if (command == "RPUSH") { |
||||||
|
std::string k, v; |
||||||
|
std::cin >> k >> v; |
||||||
|
redis.PushRight(k, v); |
||||||
|
} else if (command == "LPOP") { |
||||||
|
std::string k; |
||||||
|
std::cin >> k; |
||||||
|
string res; |
||||||
|
redis.PopLeft(k, &res); |
||||||
|
std::cout << res << std::endl; |
||||||
|
} else if (command == "RPOP") { |
||||||
|
std::string k; |
||||||
|
std::cin >> k; |
||||||
|
string res; |
||||||
|
redis.PopRight(k, &res); |
||||||
|
std::cout << res << std::endl; |
||||||
|
} else if (command == "LREM") { |
||||||
|
std::string k; |
||||||
|
int amt; |
||||||
|
std::string v; |
||||||
|
|
||||||
|
std::cin >> k >> amt >> v; |
||||||
|
std::cout << redis.Remove(k, amt, v) << std::endl; |
||||||
|
} else if (command == "LLEN") { |
||||||
|
std::string k; |
||||||
|
std::cin >> k; |
||||||
|
std::cout << redis.Length(k) << std::endl; |
||||||
|
} else if (command == "LRANGE") { |
||||||
|
std::string k; |
||||||
|
int i, j; |
||||||
|
std::cin >> k >> i >> j; |
||||||
|
std::vector<std::string> res = redis.Range(k, i, j); |
||||||
|
for (auto it = res.begin(); it != res.end(); ++it) { |
||||||
|
std::cout << " " << (*it); |
||||||
|
} |
||||||
|
std::cout << std::endl; |
||||||
|
} else if (command == "LTRIM") { |
||||||
|
std::string k; |
||||||
|
int i, j; |
||||||
|
std::cin >> k >> i >> j; |
||||||
|
redis.Trim(k, i, j); |
||||||
|
} else if (command == "LSET") { |
||||||
|
std::string k; |
||||||
|
int idx; |
||||||
|
std::string v; |
||||||
|
cin >> k >> idx >> v; |
||||||
|
redis.Set(k, idx, v); |
||||||
|
} else if (command == "LINDEX") { |
||||||
|
std::string k; |
||||||
|
int idx; |
||||||
|
std::cin >> k >> idx; |
||||||
|
string res; |
||||||
|
redis.Index(k, idx, &res); |
||||||
|
std::cout << res << std::endl; |
||||||
|
} else if (command == "PRINT") { // Added by Deon
|
||||||
|
std::string k; |
||||||
|
cin >> k; |
||||||
|
redis.Print(k); |
||||||
|
} else if (command == "QUIT") { |
||||||
|
return 0; |
||||||
|
} else { |
||||||
|
std::cout << "unknown command: " << command << std::endl; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace leveldb
|
||||||
|
|
||||||
|
|
||||||
|
// USAGE: "./redis_test" for default (unit tests)
|
||||||
|
// "./redis_test -m" for manual testing (redis command api)
|
||||||
|
// "./redis_test -m -d" for destructive manual test (erase db before use)
|
||||||
|
|
||||||
|
|
||||||
|
// Check for "want" argument in the argument list
|
||||||
|
bool found_arg(int argc, char* argv[], const char* want){ |
||||||
|
for(int i=1; i<argc; ++i){ |
||||||
|
if (strcmp(argv[i], want) == 0) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// Will run unit tests.
|
||||||
|
// However, if -m is specified, it will do user manual/interactive testing
|
||||||
|
// -m -d is manual and destructive (will clear the database before use)
|
||||||
|
int main(int argc, char* argv[]) { |
||||||
|
if (found_arg(argc, argv, "-m")) { |
||||||
|
bool destructive = found_arg(argc, argv, "-d"); |
||||||
|
return leveldb::manual_redis_test(destructive); |
||||||
|
} else { |
||||||
|
return leveldb::test::RunAllTests(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
Loading…
Reference in new issue