| // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
| // This source code is licensed under both the GPLv2 (found in the |
| // COPYING file in the root directory) and Apache 2.0 License |
| // (found in the LICENSE.Apache file in the root directory). |
| |
| #ifndef ROCKSDB_LITE |
| |
| #include <algorithm> |
| |
| #include "rocksdb/utilities/json_document.h" |
| #include "rocksdb/utilities/document_db.h" |
| |
| #include "util/testharness.h" |
| #include "util/testutil.h" |
| |
| namespace rocksdb { |
| |
| class DocumentDBTest : public testing::Test { |
| public: |
| DocumentDBTest() { |
| dbname_ = test::TmpDir() + "/document_db_test"; |
| DestroyDB(dbname_, Options()); |
| } |
| ~DocumentDBTest() { |
| delete db_; |
| DestroyDB(dbname_, Options()); |
| } |
| |
| void AssertCursorIDs(Cursor* cursor, std::vector<int64_t> expected) { |
| std::vector<int64_t> got; |
| while (cursor->Valid()) { |
| ASSERT_TRUE(cursor->Valid()); |
| ASSERT_TRUE(cursor->document().Contains("_id")); |
| got.push_back(cursor->document()["_id"].GetInt64()); |
| cursor->Next(); |
| } |
| std::sort(expected.begin(), expected.end()); |
| std::sort(got.begin(), got.end()); |
| ASSERT_TRUE(got == expected); |
| } |
| |
| // converts ' to ", so that we don't have to escape " all over the place |
| std::string ConvertQuotes(const std::string& input) { |
| std::string output; |
| for (auto x : input) { |
| if (x == '\'') { |
| output.push_back('\"'); |
| } else { |
| output.push_back(x); |
| } |
| } |
| return output; |
| } |
| |
| void CreateIndexes(std::vector<DocumentDB::IndexDescriptor> indexes) { |
| for (auto i : indexes) { |
| ASSERT_OK(db_->CreateIndex(WriteOptions(), i)); |
| } |
| } |
| |
| JSONDocument* Parse(const std::string& doc) { |
| return JSONDocument::ParseJSON(ConvertQuotes(doc).c_str()); |
| } |
| |
| std::string dbname_; |
| DocumentDB* db_; |
| }; |
| |
| TEST_F(DocumentDBTest, SimpleQueryTest) { |
| DocumentDBOptions options; |
| DocumentDB::IndexDescriptor index; |
| index.description = Parse("{\"name\": 1}"); |
| index.name = "name_index"; |
| |
| ASSERT_OK(DocumentDB::Open(options, dbname_, {}, &db_)); |
| CreateIndexes({index}); |
| delete db_; |
| // now there is index present |
| ASSERT_OK(DocumentDB::Open(options, dbname_, {index}, &db_)); |
| delete index.description; |
| |
| std::vector<std::string> json_objects = { |
| "{\"_id\': 1, \"name\": \"One\"}", "{\"_id\": 2, \"name\": \"Two\"}", |
| "{\"_id\": 3, \"name\": \"Three\"}", "{\"_id\": 4, \"name\": \"Four\"}"}; |
| |
| for (auto& json : json_objects) { |
| std::unique_ptr<JSONDocument> document(Parse(json)); |
| ASSERT_TRUE(document.get() != nullptr); |
| ASSERT_OK(db_->Insert(WriteOptions(), *document)); |
| } |
| |
| // inserting a document with existing primary key should return failure |
| { |
| std::unique_ptr<JSONDocument> document(Parse(json_objects[0])); |
| ASSERT_TRUE(document.get() != nullptr); |
| Status s = db_->Insert(WriteOptions(), *document); |
| ASSERT_TRUE(s.IsInvalidArgument()); |
| } |
| |
| // find equal to "Two" |
| { |
| std::unique_ptr<JSONDocument> query( |
| Parse("[{'$filter': {'name': 'Two', '$index': 'name_index'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {2}); |
| } |
| |
| // find less than "Three" |
| { |
| std::unique_ptr<JSONDocument> query(Parse( |
| "[{'$filter': {'name': {'$lt': 'Three'}, '$index': " |
| "'name_index'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| |
| AssertCursorIDs(cursor.get(), {1, 4}); |
| } |
| |
| // find less than "Three" without index |
| { |
| std::unique_ptr<JSONDocument> query( |
| Parse("[{'$filter': {'name': {'$lt': 'Three'} }}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {1, 4}); |
| } |
| |
| // remove less or equal to "Three" |
| { |
| std::unique_ptr<JSONDocument> query( |
| Parse("{'name': {'$lte': 'Three'}, '$index': 'name_index'}")); |
| ASSERT_OK(db_->Remove(ReadOptions(), WriteOptions(), *query)); |
| } |
| |
| // find all -- only "Two" left, everything else should be deleted |
| { |
| std::unique_ptr<JSONDocument> query(Parse("[]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {2}); |
| } |
| } |
| |
| TEST_F(DocumentDBTest, ComplexQueryTest) { |
| DocumentDBOptions options; |
| DocumentDB::IndexDescriptor priority_index; |
| priority_index.description = Parse("{'priority': 1}"); |
| priority_index.name = "priority"; |
| DocumentDB::IndexDescriptor job_name_index; |
| job_name_index.description = Parse("{'job_name': 1}"); |
| job_name_index.name = "job_name"; |
| DocumentDB::IndexDescriptor progress_index; |
| progress_index.description = Parse("{'progress': 1}"); |
| progress_index.name = "progress"; |
| |
| ASSERT_OK(DocumentDB::Open(options, dbname_, {}, &db_)); |
| CreateIndexes({priority_index, progress_index}); |
| delete priority_index.description; |
| delete progress_index.description; |
| |
| std::vector<std::string> json_objects = { |
| "{'_id': 1, 'job_name': 'play', 'priority': 10, 'progress': 14.2}", |
| "{'_id': 2, 'job_name': 'white', 'priority': 2, 'progress': 45.1}", |
| "{'_id': 3, 'job_name': 'straw', 'priority': 5, 'progress': 83.2}", |
| "{'_id': 4, 'job_name': 'temporary', 'priority': 3, 'progress': 14.9}", |
| "{'_id': 5, 'job_name': 'white', 'priority': 4, 'progress': 44.2}", |
| "{'_id': 6, 'job_name': 'tea', 'priority': 1, 'progress': 12.4}", |
| "{'_id': 7, 'job_name': 'delete', 'priority': 2, 'progress': 77.54}", |
| "{'_id': 8, 'job_name': 'rock', 'priority': 3, 'progress': 93.24}", |
| "{'_id': 9, 'job_name': 'steady', 'priority': 3, 'progress': 9.1}", |
| "{'_id': 10, 'job_name': 'white', 'priority': 1, 'progress': 61.4}", |
| "{'_id': 11, 'job_name': 'who', 'priority': 4, 'progress': 39.41}", |
| "{'_id': 12, 'job_name': 'who', 'priority': -1, 'progress': 39.42}", |
| "{'_id': 13, 'job_name': 'who', 'priority': -2, 'progress': 39.42}", }; |
| |
| // add index on the fly! |
| CreateIndexes({job_name_index}); |
| delete job_name_index.description; |
| |
| for (auto& json : json_objects) { |
| std::unique_ptr<JSONDocument> document(Parse(json)); |
| ASSERT_TRUE(document != nullptr); |
| ASSERT_OK(db_->Insert(WriteOptions(), *document)); |
| } |
| |
| // 2 < priority < 4 AND progress > 10.0, index priority |
| { |
| std::unique_ptr<JSONDocument> query(Parse( |
| "[{'$filter': {'priority': {'$lt': 4, '$gt': 2}, 'progress': {'$gt': " |
| "10.0}, '$index': 'priority'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {4, 8}); |
| } |
| |
| // -1 <= priority <= 1, index priority |
| { |
| std::unique_ptr<JSONDocument> query(Parse( |
| "[{'$filter': {'priority': {'$lte': 1, '$gte': -1}," |
| " '$index': 'priority'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {6, 10, 12}); |
| } |
| |
| // 2 < priority < 4 AND progress > 10.0, index progress |
| { |
| std::unique_ptr<JSONDocument> query(Parse( |
| "[{'$filter': {'priority': {'$lt': 4, '$gt': 2}, 'progress': {'$gt': " |
| "10.0}, '$index': 'progress'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {4, 8}); |
| } |
| |
| // job_name == 'white' AND priority >= 2, index job_name |
| { |
| std::unique_ptr<JSONDocument> query(Parse( |
| "[{'$filter': {'job_name': 'white', 'priority': {'$gte': " |
| "2}, '$index': 'job_name'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {2, 5}); |
| } |
| |
| // 35.0 <= progress < 65.5, index progress |
| { |
| std::unique_ptr<JSONDocument> query(Parse( |
| "[{'$filter': {'progress': {'$gt': 5.0, '$gte': 35.0, '$lt': 65.5}, " |
| "'$index': 'progress'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {2, 5, 10, 11, 12, 13}); |
| } |
| |
| // 2 < priority <= 4, index priority |
| { |
| std::unique_ptr<JSONDocument> query(Parse( |
| "[{'$filter': {'priority': {'$gt': 2, '$lt': 8, '$lte': 4}, " |
| "'$index': 'priority'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {4, 5, 8, 9, 11}); |
| } |
| |
| // Delete all whose progress is bigger than 50% |
| { |
| std::unique_ptr<JSONDocument> query( |
| Parse("{'progress': {'$gt': 50.0}, '$index': 'progress'}")); |
| ASSERT_OK(db_->Remove(ReadOptions(), WriteOptions(), *query)); |
| } |
| |
| // 2 < priority < 6, index priority |
| { |
| std::unique_ptr<JSONDocument> query(Parse( |
| "[{'$filter': {'priority': {'$gt': 2, '$lt': 6}, " |
| "'$index': 'priority'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| AssertCursorIDs(cursor.get(), {4, 5, 9, 11}); |
| } |
| |
| // update set priority to 10 where job_name is 'white' |
| { |
| std::unique_ptr<JSONDocument> query(Parse("{'job_name': 'white'}")); |
| std::unique_ptr<JSONDocument> update(Parse("{'$set': {'priority': 10}}")); |
| ASSERT_OK(db_->Update(ReadOptions(), WriteOptions(), *query, *update)); |
| } |
| |
| // update twice: set priority to 15 where job_name is 'white' |
| { |
| std::unique_ptr<JSONDocument> query(Parse("{'job_name': 'white'}")); |
| std::unique_ptr<JSONDocument> update(Parse("{'$set': {'priority': 10}," |
| "'$set': {'priority': 15}}")); |
| ASSERT_OK(db_->Update(ReadOptions(), WriteOptions(), *query, *update)); |
| } |
| |
| // update twice: set priority to 15 and |
| // progress to 40 where job_name is 'white' |
| { |
| std::unique_ptr<JSONDocument> query(Parse("{'job_name': 'white'}")); |
| std::unique_ptr<JSONDocument> update( |
| Parse("{'$set': {'priority': 10, 'progress': 35}," |
| "'$set': {'priority': 15, 'progress': 40}}")); |
| ASSERT_OK(db_->Update(ReadOptions(), WriteOptions(), *query, *update)); |
| } |
| |
| // priority < 0 |
| { |
| std::unique_ptr<JSONDocument> query( |
| Parse("[{'$filter': {'priority': {'$lt': 0}, '$index': 'priority'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| ASSERT_OK(cursor->status()); |
| AssertCursorIDs(cursor.get(), {12, 13}); |
| } |
| |
| // -2 < priority < 0 |
| { |
| std::unique_ptr<JSONDocument> query( |
| Parse("[{'$filter': {'priority': {'$gt': -2, '$lt': 0}," |
| " '$index': 'priority'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| ASSERT_OK(cursor->status()); |
| AssertCursorIDs(cursor.get(), {12}); |
| } |
| |
| // -2 <= priority < 0 |
| { |
| std::unique_ptr<JSONDocument> query( |
| Parse("[{'$filter': {'priority': {'$gte': -2, '$lt': 0}," |
| " '$index': 'priority'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| ASSERT_OK(cursor->status()); |
| AssertCursorIDs(cursor.get(), {12, 13}); |
| } |
| |
| // 4 < priority |
| { |
| std::unique_ptr<JSONDocument> query( |
| Parse("[{'$filter': {'priority': {'$gt': 4}, '$index': 'priority'}}]")); |
| std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); |
| ASSERT_OK(cursor->status()); |
| AssertCursorIDs(cursor.get(), {1, 2, 5}); |
| } |
| |
| Status s = db_->DropIndex("doesnt-exist"); |
| ASSERT_TRUE(!s.ok()); |
| ASSERT_OK(db_->DropIndex("priority")); |
| } |
| |
| } // namespace rocksdb |
| |
| int main(int argc, char** argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |
| |
| #else |
| #include <stdio.h> |
| |
| int main(int argc, char** argv) { |
| fprintf(stderr, "SKIPPED as DocumentDB is not supported in ROCKSDB_LITE\n"); |
| return 0; |
| } |
| |
| #endif // !ROCKSDB_LITE |