]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
11fdf7f2 TL |
2 | // This source code is licensed under both the GPLv2 (found in the |
3 | // COPYING file in the root directory) and Apache 2.0 License | |
4 | // (found in the LICENSE.Apache file in the root directory). | |
7c673cae FG |
5 | |
6 | #ifndef ROCKSDB_LITE | |
7 | ||
8 | #include <algorithm> | |
9 | ||
10 | #include "rocksdb/utilities/json_document.h" | |
11 | #include "rocksdb/utilities/document_db.h" | |
12 | ||
13 | #include "util/testharness.h" | |
14 | #include "util/testutil.h" | |
15 | ||
16 | namespace rocksdb { | |
17 | ||
18 | class DocumentDBTest : public testing::Test { | |
19 | public: | |
20 | DocumentDBTest() { | |
11fdf7f2 | 21 | dbname_ = test::PerThreadDBPath("document_db_test"); |
7c673cae FG |
22 | DestroyDB(dbname_, Options()); |
23 | } | |
24 | ~DocumentDBTest() { | |
25 | delete db_; | |
26 | DestroyDB(dbname_, Options()); | |
27 | } | |
28 | ||
29 | void AssertCursorIDs(Cursor* cursor, std::vector<int64_t> expected) { | |
30 | std::vector<int64_t> got; | |
31 | while (cursor->Valid()) { | |
32 | ASSERT_TRUE(cursor->Valid()); | |
33 | ASSERT_TRUE(cursor->document().Contains("_id")); | |
34 | got.push_back(cursor->document()["_id"].GetInt64()); | |
35 | cursor->Next(); | |
36 | } | |
37 | std::sort(expected.begin(), expected.end()); | |
38 | std::sort(got.begin(), got.end()); | |
39 | ASSERT_TRUE(got == expected); | |
40 | } | |
41 | ||
42 | // converts ' to ", so that we don't have to escape " all over the place | |
43 | std::string ConvertQuotes(const std::string& input) { | |
44 | std::string output; | |
45 | for (auto x : input) { | |
46 | if (x == '\'') { | |
47 | output.push_back('\"'); | |
48 | } else { | |
49 | output.push_back(x); | |
50 | } | |
51 | } | |
52 | return output; | |
53 | } | |
54 | ||
55 | void CreateIndexes(std::vector<DocumentDB::IndexDescriptor> indexes) { | |
56 | for (auto i : indexes) { | |
57 | ASSERT_OK(db_->CreateIndex(WriteOptions(), i)); | |
58 | } | |
59 | } | |
60 | ||
61 | JSONDocument* Parse(const std::string& doc) { | |
62 | return JSONDocument::ParseJSON(ConvertQuotes(doc).c_str()); | |
63 | } | |
64 | ||
65 | std::string dbname_; | |
66 | DocumentDB* db_; | |
67 | }; | |
68 | ||
69 | TEST_F(DocumentDBTest, SimpleQueryTest) { | |
70 | DocumentDBOptions options; | |
71 | DocumentDB::IndexDescriptor index; | |
72 | index.description = Parse("{\"name\": 1}"); | |
73 | index.name = "name_index"; | |
74 | ||
75 | ASSERT_OK(DocumentDB::Open(options, dbname_, {}, &db_)); | |
76 | CreateIndexes({index}); | |
77 | delete db_; | |
11fdf7f2 | 78 | db_ = nullptr; |
7c673cae FG |
79 | // now there is index present |
80 | ASSERT_OK(DocumentDB::Open(options, dbname_, {index}, &db_)); | |
11fdf7f2 | 81 | assert(db_ != nullptr); |
7c673cae FG |
82 | delete index.description; |
83 | ||
84 | std::vector<std::string> json_objects = { | |
85 | "{\"_id\': 1, \"name\": \"One\"}", "{\"_id\": 2, \"name\": \"Two\"}", | |
86 | "{\"_id\": 3, \"name\": \"Three\"}", "{\"_id\": 4, \"name\": \"Four\"}"}; | |
87 | ||
88 | for (auto& json : json_objects) { | |
89 | std::unique_ptr<JSONDocument> document(Parse(json)); | |
90 | ASSERT_TRUE(document.get() != nullptr); | |
91 | ASSERT_OK(db_->Insert(WriteOptions(), *document)); | |
92 | } | |
93 | ||
94 | // inserting a document with existing primary key should return failure | |
95 | { | |
96 | std::unique_ptr<JSONDocument> document(Parse(json_objects[0])); | |
97 | ASSERT_TRUE(document.get() != nullptr); | |
98 | Status s = db_->Insert(WriteOptions(), *document); | |
99 | ASSERT_TRUE(s.IsInvalidArgument()); | |
100 | } | |
101 | ||
102 | // find equal to "Two" | |
103 | { | |
104 | std::unique_ptr<JSONDocument> query( | |
105 | Parse("[{'$filter': {'name': 'Two', '$index': 'name_index'}}]")); | |
106 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
107 | AssertCursorIDs(cursor.get(), {2}); | |
108 | } | |
109 | ||
110 | // find less than "Three" | |
111 | { | |
112 | std::unique_ptr<JSONDocument> query(Parse( | |
113 | "[{'$filter': {'name': {'$lt': 'Three'}, '$index': " | |
114 | "'name_index'}}]")); | |
115 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
116 | ||
117 | AssertCursorIDs(cursor.get(), {1, 4}); | |
118 | } | |
119 | ||
120 | // find less than "Three" without index | |
121 | { | |
122 | std::unique_ptr<JSONDocument> query( | |
123 | Parse("[{'$filter': {'name': {'$lt': 'Three'} }}]")); | |
124 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
125 | AssertCursorIDs(cursor.get(), {1, 4}); | |
126 | } | |
127 | ||
128 | // remove less or equal to "Three" | |
129 | { | |
130 | std::unique_ptr<JSONDocument> query( | |
131 | Parse("{'name': {'$lte': 'Three'}, '$index': 'name_index'}")); | |
132 | ASSERT_OK(db_->Remove(ReadOptions(), WriteOptions(), *query)); | |
133 | } | |
134 | ||
135 | // find all -- only "Two" left, everything else should be deleted | |
136 | { | |
137 | std::unique_ptr<JSONDocument> query(Parse("[]")); | |
138 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
139 | AssertCursorIDs(cursor.get(), {2}); | |
140 | } | |
141 | } | |
142 | ||
143 | TEST_F(DocumentDBTest, ComplexQueryTest) { | |
144 | DocumentDBOptions options; | |
145 | DocumentDB::IndexDescriptor priority_index; | |
146 | priority_index.description = Parse("{'priority': 1}"); | |
147 | priority_index.name = "priority"; | |
148 | DocumentDB::IndexDescriptor job_name_index; | |
149 | job_name_index.description = Parse("{'job_name': 1}"); | |
150 | job_name_index.name = "job_name"; | |
151 | DocumentDB::IndexDescriptor progress_index; | |
152 | progress_index.description = Parse("{'progress': 1}"); | |
153 | progress_index.name = "progress"; | |
154 | ||
155 | ASSERT_OK(DocumentDB::Open(options, dbname_, {}, &db_)); | |
156 | CreateIndexes({priority_index, progress_index}); | |
157 | delete priority_index.description; | |
158 | delete progress_index.description; | |
159 | ||
160 | std::vector<std::string> json_objects = { | |
161 | "{'_id': 1, 'job_name': 'play', 'priority': 10, 'progress': 14.2}", | |
162 | "{'_id': 2, 'job_name': 'white', 'priority': 2, 'progress': 45.1}", | |
163 | "{'_id': 3, 'job_name': 'straw', 'priority': 5, 'progress': 83.2}", | |
164 | "{'_id': 4, 'job_name': 'temporary', 'priority': 3, 'progress': 14.9}", | |
165 | "{'_id': 5, 'job_name': 'white', 'priority': 4, 'progress': 44.2}", | |
166 | "{'_id': 6, 'job_name': 'tea', 'priority': 1, 'progress': 12.4}", | |
167 | "{'_id': 7, 'job_name': 'delete', 'priority': 2, 'progress': 77.54}", | |
168 | "{'_id': 8, 'job_name': 'rock', 'priority': 3, 'progress': 93.24}", | |
169 | "{'_id': 9, 'job_name': 'steady', 'priority': 3, 'progress': 9.1}", | |
170 | "{'_id': 10, 'job_name': 'white', 'priority': 1, 'progress': 61.4}", | |
171 | "{'_id': 11, 'job_name': 'who', 'priority': 4, 'progress': 39.41}", | |
172 | "{'_id': 12, 'job_name': 'who', 'priority': -1, 'progress': 39.42}", | |
173 | "{'_id': 13, 'job_name': 'who', 'priority': -2, 'progress': 39.42}", }; | |
174 | ||
175 | // add index on the fly! | |
176 | CreateIndexes({job_name_index}); | |
177 | delete job_name_index.description; | |
178 | ||
179 | for (auto& json : json_objects) { | |
180 | std::unique_ptr<JSONDocument> document(Parse(json)); | |
181 | ASSERT_TRUE(document != nullptr); | |
182 | ASSERT_OK(db_->Insert(WriteOptions(), *document)); | |
183 | } | |
184 | ||
185 | // 2 < priority < 4 AND progress > 10.0, index priority | |
186 | { | |
187 | std::unique_ptr<JSONDocument> query(Parse( | |
188 | "[{'$filter': {'priority': {'$lt': 4, '$gt': 2}, 'progress': {'$gt': " | |
189 | "10.0}, '$index': 'priority'}}]")); | |
190 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
191 | AssertCursorIDs(cursor.get(), {4, 8}); | |
192 | } | |
193 | ||
194 | // -1 <= priority <= 1, index priority | |
195 | { | |
196 | std::unique_ptr<JSONDocument> query(Parse( | |
197 | "[{'$filter': {'priority': {'$lte': 1, '$gte': -1}," | |
198 | " '$index': 'priority'}}]")); | |
199 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
200 | AssertCursorIDs(cursor.get(), {6, 10, 12}); | |
201 | } | |
202 | ||
203 | // 2 < priority < 4 AND progress > 10.0, index progress | |
204 | { | |
205 | std::unique_ptr<JSONDocument> query(Parse( | |
206 | "[{'$filter': {'priority': {'$lt': 4, '$gt': 2}, 'progress': {'$gt': " | |
207 | "10.0}, '$index': 'progress'}}]")); | |
208 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
209 | AssertCursorIDs(cursor.get(), {4, 8}); | |
210 | } | |
211 | ||
212 | // job_name == 'white' AND priority >= 2, index job_name | |
213 | { | |
214 | std::unique_ptr<JSONDocument> query(Parse( | |
215 | "[{'$filter': {'job_name': 'white', 'priority': {'$gte': " | |
216 | "2}, '$index': 'job_name'}}]")); | |
217 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
218 | AssertCursorIDs(cursor.get(), {2, 5}); | |
219 | } | |
220 | ||
221 | // 35.0 <= progress < 65.5, index progress | |
222 | { | |
223 | std::unique_ptr<JSONDocument> query(Parse( | |
224 | "[{'$filter': {'progress': {'$gt': 5.0, '$gte': 35.0, '$lt': 65.5}, " | |
225 | "'$index': 'progress'}}]")); | |
226 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
227 | AssertCursorIDs(cursor.get(), {2, 5, 10, 11, 12, 13}); | |
228 | } | |
229 | ||
230 | // 2 < priority <= 4, index priority | |
231 | { | |
232 | std::unique_ptr<JSONDocument> query(Parse( | |
233 | "[{'$filter': {'priority': {'$gt': 2, '$lt': 8, '$lte': 4}, " | |
234 | "'$index': 'priority'}}]")); | |
235 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
236 | AssertCursorIDs(cursor.get(), {4, 5, 8, 9, 11}); | |
237 | } | |
238 | ||
239 | // Delete all whose progress is bigger than 50% | |
240 | { | |
241 | std::unique_ptr<JSONDocument> query( | |
242 | Parse("{'progress': {'$gt': 50.0}, '$index': 'progress'}")); | |
243 | ASSERT_OK(db_->Remove(ReadOptions(), WriteOptions(), *query)); | |
244 | } | |
245 | ||
246 | // 2 < priority < 6, index priority | |
247 | { | |
248 | std::unique_ptr<JSONDocument> query(Parse( | |
249 | "[{'$filter': {'priority': {'$gt': 2, '$lt': 6}, " | |
250 | "'$index': 'priority'}}]")); | |
251 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
252 | AssertCursorIDs(cursor.get(), {4, 5, 9, 11}); | |
253 | } | |
254 | ||
255 | // update set priority to 10 where job_name is 'white' | |
256 | { | |
257 | std::unique_ptr<JSONDocument> query(Parse("{'job_name': 'white'}")); | |
258 | std::unique_ptr<JSONDocument> update(Parse("{'$set': {'priority': 10}}")); | |
259 | ASSERT_OK(db_->Update(ReadOptions(), WriteOptions(), *query, *update)); | |
260 | } | |
261 | ||
262 | // update twice: set priority to 15 where job_name is 'white' | |
263 | { | |
264 | std::unique_ptr<JSONDocument> query(Parse("{'job_name': 'white'}")); | |
265 | std::unique_ptr<JSONDocument> update(Parse("{'$set': {'priority': 10}," | |
266 | "'$set': {'priority': 15}}")); | |
267 | ASSERT_OK(db_->Update(ReadOptions(), WriteOptions(), *query, *update)); | |
268 | } | |
269 | ||
270 | // update twice: set priority to 15 and | |
271 | // progress to 40 where job_name is 'white' | |
272 | { | |
273 | std::unique_ptr<JSONDocument> query(Parse("{'job_name': 'white'}")); | |
274 | std::unique_ptr<JSONDocument> update( | |
275 | Parse("{'$set': {'priority': 10, 'progress': 35}," | |
276 | "'$set': {'priority': 15, 'progress': 40}}")); | |
277 | ASSERT_OK(db_->Update(ReadOptions(), WriteOptions(), *query, *update)); | |
278 | } | |
279 | ||
280 | // priority < 0 | |
281 | { | |
282 | std::unique_ptr<JSONDocument> query( | |
283 | Parse("[{'$filter': {'priority': {'$lt': 0}, '$index': 'priority'}}]")); | |
284 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
285 | ASSERT_OK(cursor->status()); | |
286 | AssertCursorIDs(cursor.get(), {12, 13}); | |
287 | } | |
288 | ||
289 | // -2 < priority < 0 | |
290 | { | |
291 | std::unique_ptr<JSONDocument> query( | |
292 | Parse("[{'$filter': {'priority': {'$gt': -2, '$lt': 0}," | |
293 | " '$index': 'priority'}}]")); | |
294 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
295 | ASSERT_OK(cursor->status()); | |
296 | AssertCursorIDs(cursor.get(), {12}); | |
297 | } | |
298 | ||
299 | // -2 <= priority < 0 | |
300 | { | |
301 | std::unique_ptr<JSONDocument> query( | |
302 | Parse("[{'$filter': {'priority': {'$gte': -2, '$lt': 0}," | |
303 | " '$index': 'priority'}}]")); | |
304 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
305 | ASSERT_OK(cursor->status()); | |
306 | AssertCursorIDs(cursor.get(), {12, 13}); | |
307 | } | |
308 | ||
309 | // 4 < priority | |
310 | { | |
311 | std::unique_ptr<JSONDocument> query( | |
312 | Parse("[{'$filter': {'priority': {'$gt': 4}, '$index': 'priority'}}]")); | |
313 | std::unique_ptr<Cursor> cursor(db_->Query(ReadOptions(), *query)); | |
314 | ASSERT_OK(cursor->status()); | |
315 | AssertCursorIDs(cursor.get(), {1, 2, 5}); | |
316 | } | |
317 | ||
318 | Status s = db_->DropIndex("doesnt-exist"); | |
319 | ASSERT_TRUE(!s.ok()); | |
320 | ASSERT_OK(db_->DropIndex("priority")); | |
321 | } | |
322 | ||
323 | } // namespace rocksdb | |
324 | ||
325 | int main(int argc, char** argv) { | |
326 | ::testing::InitGoogleTest(&argc, argv); | |
327 | return RUN_ALL_TESTS(); | |
328 | } | |
329 | ||
330 | #else | |
331 | #include <stdio.h> | |
332 | ||
11fdf7f2 | 333 | int main(int /*argc*/, char** /*argv*/) { |
7c673cae FG |
334 | fprintf(stderr, "SKIPPED as DocumentDB is not supported in ROCKSDB_LITE\n"); |
335 | return 0; | |
336 | } | |
337 | ||
338 | #endif // !ROCKSDB_LITE |