stringappend_test.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. /**
  2. * An persistent map : key -> (list of strings), using rocksdb merge.
  3. * This file is a test-harness / use-case for the StringAppendOperator.
  4. *
  5. * @author Deon Nicholas (dnicholas@fb.com)
  6. * Copyright 2013 Facebook, Inc.
  7. */
  8. #include <iostream>
  9. #include <map>
  10. #include "rocksdb/db.h"
  11. #include "rocksdb/merge_operator.h"
  12. #include "rocksdb/utilities/db_ttl.h"
  13. #include "test_util/testharness.h"
  14. #include "util/random.h"
  15. #include "utilities/merge_operators.h"
  16. #include "utilities/merge_operators/string_append/stringappend.h"
  17. #include "utilities/merge_operators/string_append/stringappend2.h"
  18. using namespace ROCKSDB_NAMESPACE;
  19. namespace ROCKSDB_NAMESPACE {
  20. // Path to the database on file system
  21. const std::string kDbName = test::PerThreadDBPath("stringappend_test");
  22. namespace {
  23. // OpenDb opens a (possibly new) rocksdb database with a StringAppendOperator
  24. std::shared_ptr<DB> OpenNormalDb(char delim_char) {
  25. DB* db;
  26. Options options;
  27. options.create_if_missing = true;
  28. options.merge_operator.reset(new StringAppendOperator(delim_char));
  29. EXPECT_OK(DB::Open(options, kDbName, &db));
  30. return std::shared_ptr<DB>(db);
  31. }
  32. #ifndef ROCKSDB_LITE // TtlDb is not supported in Lite
  33. // Open a TtlDB with a non-associative StringAppendTESTOperator
  34. std::shared_ptr<DB> OpenTtlDb(char delim_char) {
  35. DBWithTTL* db;
  36. Options options;
  37. options.create_if_missing = true;
  38. options.merge_operator.reset(new StringAppendTESTOperator(delim_char));
  39. EXPECT_OK(DBWithTTL::Open(options, kDbName, &db, 123456));
  40. return std::shared_ptr<DB>(db);
  41. }
  42. #endif // !ROCKSDB_LITE
  43. } // namespace
  44. /// StringLists represents a set of string-lists, each with a key-index.
  45. /// Supports Append(list, string) and Get(list)
  46. class StringLists {
  47. public:
  48. //Constructor: specifies the rocksdb db
  49. /* implicit */
  50. StringLists(std::shared_ptr<DB> db)
  51. : db_(db),
  52. merge_option_(),
  53. get_option_() {
  54. assert(db);
  55. }
  56. // Append string val onto the list defined by key; return true on success
  57. bool Append(const std::string& key, const std::string& val){
  58. Slice valSlice(val.data(), val.size());
  59. auto s = db_->Merge(merge_option_, key, valSlice);
  60. if (s.ok()) {
  61. return true;
  62. } else {
  63. std::cerr << "ERROR " << s.ToString() << std::endl;
  64. return false;
  65. }
  66. }
  67. // Returns the list of strings associated with key (or "" if does not exist)
  68. bool Get(const std::string& key, std::string* const result){
  69. assert(result != nullptr); // we should have a place to store the result
  70. auto s = db_->Get(get_option_, key, result);
  71. if (s.ok()) {
  72. return true;
  73. }
  74. // Either key does not exist, or there is some error.
  75. *result = ""; // Always return empty string (just for convention)
  76. //NotFound is okay; just return empty (similar to std::map)
  77. //But network or db errors, etc, should fail the test (or at least yell)
  78. if (!s.IsNotFound()) {
  79. std::cerr << "ERROR " << s.ToString() << std::endl;
  80. }
  81. // Always return false if s.ok() was not true
  82. return false;
  83. }
  84. private:
  85. std::shared_ptr<DB> db_;
  86. WriteOptions merge_option_;
  87. ReadOptions get_option_;
  88. };
  89. // The class for unit-testing
  90. class StringAppendOperatorTest : public testing::Test {
  91. public:
  92. StringAppendOperatorTest() {
  93. DestroyDB(kDbName, Options()); // Start each test with a fresh DB
  94. }
  95. typedef std::shared_ptr<DB> (* OpenFuncPtr)(char);
  96. // Allows user to open databases with different configurations.
  97. // e.g.: Can open a DB or a TtlDB, etc.
  98. static void SetOpenDbFunction(OpenFuncPtr func) {
  99. OpenDb = func;
  100. }
  101. protected:
  102. static OpenFuncPtr OpenDb;
  103. };
  104. StringAppendOperatorTest::OpenFuncPtr StringAppendOperatorTest::OpenDb = nullptr;
  105. // THE TEST CASES BEGIN HERE
  106. TEST_F(StringAppendOperatorTest, IteratorTest) {
  107. auto db_ = OpenDb(',');
  108. StringLists slists(db_);
  109. slists.Append("k1", "v1");
  110. slists.Append("k1", "v2");
  111. slists.Append("k1", "v3");
  112. slists.Append("k2", "a1");
  113. slists.Append("k2", "a2");
  114. slists.Append("k2", "a3");
  115. std::string res;
  116. std::unique_ptr<ROCKSDB_NAMESPACE::Iterator> it(
  117. db_->NewIterator(ReadOptions()));
  118. std::string k1("k1");
  119. std::string k2("k2");
  120. bool first = true;
  121. for (it->Seek(k1); it->Valid(); it->Next()) {
  122. res = it->value().ToString();
  123. if (first) {
  124. ASSERT_EQ(res, "v1,v2,v3");
  125. first = false;
  126. } else {
  127. ASSERT_EQ(res, "a1,a2,a3");
  128. }
  129. }
  130. slists.Append("k2", "a4");
  131. slists.Append("k1", "v4");
  132. // Snapshot should still be the same. Should ignore a4 and v4.
  133. first = true;
  134. for (it->Seek(k1); it->Valid(); it->Next()) {
  135. res = it->value().ToString();
  136. if (first) {
  137. ASSERT_EQ(res, "v1,v2,v3");
  138. first = false;
  139. } else {
  140. ASSERT_EQ(res, "a1,a2,a3");
  141. }
  142. }
  143. // Should release the snapshot and be aware of the new stuff now
  144. it.reset(db_->NewIterator(ReadOptions()));
  145. first = true;
  146. for (it->Seek(k1); it->Valid(); it->Next()) {
  147. res = it->value().ToString();
  148. if (first) {
  149. ASSERT_EQ(res, "v1,v2,v3,v4");
  150. first = false;
  151. } else {
  152. ASSERT_EQ(res, "a1,a2,a3,a4");
  153. }
  154. }
  155. // start from k2 this time.
  156. for (it->Seek(k2); it->Valid(); it->Next()) {
  157. res = it->value().ToString();
  158. if (first) {
  159. ASSERT_EQ(res, "v1,v2,v3,v4");
  160. first = false;
  161. } else {
  162. ASSERT_EQ(res, "a1,a2,a3,a4");
  163. }
  164. }
  165. slists.Append("k3", "g1");
  166. it.reset(db_->NewIterator(ReadOptions()));
  167. first = true;
  168. std::string k3("k3");
  169. for(it->Seek(k2); it->Valid(); it->Next()) {
  170. res = it->value().ToString();
  171. if (first) {
  172. ASSERT_EQ(res, "a1,a2,a3,a4");
  173. first = false;
  174. } else {
  175. ASSERT_EQ(res, "g1");
  176. }
  177. }
  178. for(it->Seek(k3); it->Valid(); it->Next()) {
  179. res = it->value().ToString();
  180. if (first) {
  181. // should not be hit
  182. ASSERT_EQ(res, "a1,a2,a3,a4");
  183. first = false;
  184. } else {
  185. ASSERT_EQ(res, "g1");
  186. }
  187. }
  188. }
  189. TEST_F(StringAppendOperatorTest, SimpleTest) {
  190. auto db = OpenDb(',');
  191. StringLists slists(db);
  192. slists.Append("k1", "v1");
  193. slists.Append("k1", "v2");
  194. slists.Append("k1", "v3");
  195. std::string res;
  196. bool status = slists.Get("k1", &res);
  197. ASSERT_TRUE(status);
  198. ASSERT_EQ(res, "v1,v2,v3");
  199. }
  200. TEST_F(StringAppendOperatorTest, SimpleDelimiterTest) {
  201. auto db = OpenDb('|');
  202. StringLists slists(db);
  203. slists.Append("k1", "v1");
  204. slists.Append("k1", "v2");
  205. slists.Append("k1", "v3");
  206. std::string res;
  207. slists.Get("k1", &res);
  208. ASSERT_EQ(res, "v1|v2|v3");
  209. }
  210. TEST_F(StringAppendOperatorTest, OneValueNoDelimiterTest) {
  211. auto db = OpenDb('!');
  212. StringLists slists(db);
  213. slists.Append("random_key", "single_val");
  214. std::string res;
  215. slists.Get("random_key", &res);
  216. ASSERT_EQ(res, "single_val");
  217. }
  218. TEST_F(StringAppendOperatorTest, VariousKeys) {
  219. auto db = OpenDb('\n');
  220. StringLists slists(db);
  221. slists.Append("c", "asdasd");
  222. slists.Append("a", "x");
  223. slists.Append("b", "y");
  224. slists.Append("a", "t");
  225. slists.Append("a", "r");
  226. slists.Append("b", "2");
  227. slists.Append("c", "asdasd");
  228. std::string a, b, c;
  229. bool sa, sb, sc;
  230. sa = slists.Get("a", &a);
  231. sb = slists.Get("b", &b);
  232. sc = slists.Get("c", &c);
  233. ASSERT_TRUE(sa && sb && sc); // All three keys should have been found
  234. ASSERT_EQ(a, "x\nt\nr");
  235. ASSERT_EQ(b, "y\n2");
  236. ASSERT_EQ(c, "asdasd\nasdasd");
  237. }
  238. // Generate semi random keys/words from a small distribution.
  239. TEST_F(StringAppendOperatorTest, RandomMixGetAppend) {
  240. auto db = OpenDb(' ');
  241. StringLists slists(db);
  242. // Generate a list of random keys and values
  243. const int kWordCount = 15;
  244. std::string words[] = {"sdasd", "triejf", "fnjsdfn", "dfjisdfsf", "342839",
  245. "dsuha", "mabuais", "sadajsid", "jf9834hf", "2d9j89",
  246. "dj9823jd", "a", "dk02ed2dh", "$(jd4h984$(*", "mabz"};
  247. const int kKeyCount = 6;
  248. std::string keys[] = {"dhaiusdhu", "denidw", "daisda", "keykey", "muki",
  249. "shzassdianmd"};
  250. // Will store a local copy of all data in order to verify correctness
  251. std::map<std::string, std::string> parallel_copy;
  252. // Generate a bunch of random queries (Append and Get)!
  253. enum query_t { APPEND_OP, GET_OP, NUM_OPS };
  254. Random randomGen(1337); //deterministic seed; always get same results!
  255. const int kNumQueries = 30;
  256. for (int q=0; q<kNumQueries; ++q) {
  257. // Generate a random query (Append or Get) and random parameters
  258. query_t query = (query_t)randomGen.Uniform((int)NUM_OPS);
  259. std::string key = keys[randomGen.Uniform((int)kKeyCount)];
  260. std::string word = words[randomGen.Uniform((int)kWordCount)];
  261. // Apply the query and any checks.
  262. if (query == APPEND_OP) {
  263. // Apply the rocksdb test-harness Append defined above
  264. slists.Append(key, word); //apply the rocksdb append
  265. // Apply the similar "Append" to the parallel copy
  266. if (parallel_copy[key].size() > 0) {
  267. parallel_copy[key] += " " + word;
  268. } else {
  269. parallel_copy[key] = word;
  270. }
  271. } else if (query == GET_OP) {
  272. // Assumes that a non-existent key just returns <empty>
  273. std::string res;
  274. slists.Get(key, &res);
  275. ASSERT_EQ(res, parallel_copy[key]);
  276. }
  277. }
  278. }
  279. TEST_F(StringAppendOperatorTest, BIGRandomMixGetAppend) {
  280. auto db = OpenDb(' ');
  281. StringLists slists(db);
  282. // Generate a list of random keys and values
  283. const int kWordCount = 15;
  284. std::string words[] = {"sdasd", "triejf", "fnjsdfn", "dfjisdfsf", "342839",
  285. "dsuha", "mabuais", "sadajsid", "jf9834hf", "2d9j89",
  286. "dj9823jd", "a", "dk02ed2dh", "$(jd4h984$(*", "mabz"};
  287. const int kKeyCount = 6;
  288. std::string keys[] = {"dhaiusdhu", "denidw", "daisda", "keykey", "muki",
  289. "shzassdianmd"};
  290. // Will store a local copy of all data in order to verify correctness
  291. std::map<std::string, std::string> parallel_copy;
  292. // Generate a bunch of random queries (Append and Get)!
  293. enum query_t { APPEND_OP, GET_OP, NUM_OPS };
  294. Random randomGen(9138204); // deterministic seed
  295. const int kNumQueries = 1000;
  296. for (int q=0; q<kNumQueries; ++q) {
  297. // Generate a random query (Append or Get) and random parameters
  298. query_t query = (query_t)randomGen.Uniform((int)NUM_OPS);
  299. std::string key = keys[randomGen.Uniform((int)kKeyCount)];
  300. std::string word = words[randomGen.Uniform((int)kWordCount)];
  301. //Apply the query and any checks.
  302. if (query == APPEND_OP) {
  303. // Apply the rocksdb test-harness Append defined above
  304. slists.Append(key, word); //apply the rocksdb append
  305. // Apply the similar "Append" to the parallel copy
  306. if (parallel_copy[key].size() > 0) {
  307. parallel_copy[key] += " " + word;
  308. } else {
  309. parallel_copy[key] = word;
  310. }
  311. } else if (query == GET_OP) {
  312. // Assumes that a non-existent key just returns <empty>
  313. std::string res;
  314. slists.Get(key, &res);
  315. ASSERT_EQ(res, parallel_copy[key]);
  316. }
  317. }
  318. }
  319. TEST_F(StringAppendOperatorTest, PersistentVariousKeys) {
  320. // Perform the following operations in limited scope
  321. {
  322. auto db = OpenDb('\n');
  323. StringLists slists(db);
  324. slists.Append("c", "asdasd");
  325. slists.Append("a", "x");
  326. slists.Append("b", "y");
  327. slists.Append("a", "t");
  328. slists.Append("a", "r");
  329. slists.Append("b", "2");
  330. slists.Append("c", "asdasd");
  331. std::string a, b, c;
  332. slists.Get("a", &a);
  333. slists.Get("b", &b);
  334. slists.Get("c", &c);
  335. ASSERT_EQ(a, "x\nt\nr");
  336. ASSERT_EQ(b, "y\n2");
  337. ASSERT_EQ(c, "asdasd\nasdasd");
  338. }
  339. // Reopen the database (the previous changes should persist / be remembered)
  340. {
  341. auto db = OpenDb('\n');
  342. StringLists slists(db);
  343. slists.Append("c", "bbnagnagsx");
  344. slists.Append("a", "sa");
  345. slists.Append("b", "df");
  346. slists.Append("a", "gh");
  347. slists.Append("a", "jk");
  348. slists.Append("b", "l;");
  349. slists.Append("c", "rogosh");
  350. // The previous changes should be on disk (L0)
  351. // The most recent changes should be in memory (MemTable)
  352. // Hence, this will test both Get() paths.
  353. std::string a, b, c;
  354. slists.Get("a", &a);
  355. slists.Get("b", &b);
  356. slists.Get("c", &c);
  357. ASSERT_EQ(a, "x\nt\nr\nsa\ngh\njk");
  358. ASSERT_EQ(b, "y\n2\ndf\nl;");
  359. ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx\nrogosh");
  360. }
  361. // Reopen the database (the previous changes should persist / be remembered)
  362. {
  363. auto db = OpenDb('\n');
  364. StringLists slists(db);
  365. // All changes should be on disk. This will test VersionSet Get()
  366. std::string a, b, c;
  367. slists.Get("a", &a);
  368. slists.Get("b", &b);
  369. slists.Get("c", &c);
  370. ASSERT_EQ(a, "x\nt\nr\nsa\ngh\njk");
  371. ASSERT_EQ(b, "y\n2\ndf\nl;");
  372. ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx\nrogosh");
  373. }
  374. }
  375. TEST_F(StringAppendOperatorTest, PersistentFlushAndCompaction) {
  376. // Perform the following operations in limited scope
  377. {
  378. auto db = OpenDb('\n');
  379. StringLists slists(db);
  380. std::string a, b, c;
  381. bool success;
  382. // Append, Flush, Get
  383. slists.Append("c", "asdasd");
  384. db->Flush(ROCKSDB_NAMESPACE::FlushOptions());
  385. success = slists.Get("c", &c);
  386. ASSERT_TRUE(success);
  387. ASSERT_EQ(c, "asdasd");
  388. // Append, Flush, Append, Get
  389. slists.Append("a", "x");
  390. slists.Append("b", "y");
  391. db->Flush(ROCKSDB_NAMESPACE::FlushOptions());
  392. slists.Append("a", "t");
  393. slists.Append("a", "r");
  394. slists.Append("b", "2");
  395. success = slists.Get("a", &a);
  396. assert(success == true);
  397. ASSERT_EQ(a, "x\nt\nr");
  398. success = slists.Get("b", &b);
  399. assert(success == true);
  400. ASSERT_EQ(b, "y\n2");
  401. // Append, Get
  402. success = slists.Append("c", "asdasd");
  403. assert(success);
  404. success = slists.Append("b", "monkey");
  405. assert(success);
  406. // I omit the "assert(success)" checks here.
  407. slists.Get("a", &a);
  408. slists.Get("b", &b);
  409. slists.Get("c", &c);
  410. ASSERT_EQ(a, "x\nt\nr");
  411. ASSERT_EQ(b, "y\n2\nmonkey");
  412. ASSERT_EQ(c, "asdasd\nasdasd");
  413. }
  414. // Reopen the database (the previous changes should persist / be remembered)
  415. {
  416. auto db = OpenDb('\n');
  417. StringLists slists(db);
  418. std::string a, b, c;
  419. // Get (Quick check for persistence of previous database)
  420. slists.Get("a", &a);
  421. ASSERT_EQ(a, "x\nt\nr");
  422. //Append, Compact, Get
  423. slists.Append("c", "bbnagnagsx");
  424. slists.Append("a", "sa");
  425. slists.Append("b", "df");
  426. db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
  427. slists.Get("a", &a);
  428. slists.Get("b", &b);
  429. slists.Get("c", &c);
  430. ASSERT_EQ(a, "x\nt\nr\nsa");
  431. ASSERT_EQ(b, "y\n2\nmonkey\ndf");
  432. ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx");
  433. // Append, Get
  434. slists.Append("a", "gh");
  435. slists.Append("a", "jk");
  436. slists.Append("b", "l;");
  437. slists.Append("c", "rogosh");
  438. slists.Get("a", &a);
  439. slists.Get("b", &b);
  440. slists.Get("c", &c);
  441. ASSERT_EQ(a, "x\nt\nr\nsa\ngh\njk");
  442. ASSERT_EQ(b, "y\n2\nmonkey\ndf\nl;");
  443. ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx\nrogosh");
  444. // Compact, Get
  445. db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
  446. ASSERT_EQ(a, "x\nt\nr\nsa\ngh\njk");
  447. ASSERT_EQ(b, "y\n2\nmonkey\ndf\nl;");
  448. ASSERT_EQ(c, "asdasd\nasdasd\nbbnagnagsx\nrogosh");
  449. // Append, Flush, Compact, Get
  450. slists.Append("b", "afcg");
  451. db->Flush(ROCKSDB_NAMESPACE::FlushOptions());
  452. db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
  453. slists.Get("b", &b);
  454. ASSERT_EQ(b, "y\n2\nmonkey\ndf\nl;\nafcg");
  455. }
  456. }
  457. TEST_F(StringAppendOperatorTest, SimpleTestNullDelimiter) {
  458. auto db = OpenDb('\0');
  459. StringLists slists(db);
  460. slists.Append("k1", "v1");
  461. slists.Append("k1", "v2");
  462. slists.Append("k1", "v3");
  463. std::string res;
  464. bool status = slists.Get("k1", &res);
  465. ASSERT_TRUE(status);
  466. // Construct the desired string. Default constructor doesn't like '\0' chars.
  467. std::string checker("v1,v2,v3"); // Verify that the string is right size.
  468. checker[2] = '\0'; // Use null delimiter instead of comma.
  469. checker[5] = '\0';
  470. assert(checker.size() == 8); // Verify it is still the correct size
  471. // Check that the rocksdb result string matches the desired string
  472. assert(res.size() == checker.size());
  473. ASSERT_EQ(res, checker);
  474. }
  475. } // namespace ROCKSDB_NAMESPACE
  476. int main(int argc, char** argv) {
  477. ::testing::InitGoogleTest(&argc, argv);
  478. // Run with regular database
  479. int result;
  480. {
  481. fprintf(stderr, "Running tests with regular db and operator.\n");
  482. StringAppendOperatorTest::SetOpenDbFunction(&OpenNormalDb);
  483. result = RUN_ALL_TESTS();
  484. }
  485. #ifndef ROCKSDB_LITE // TtlDb is not supported in Lite
  486. // Run with TTL
  487. {
  488. fprintf(stderr, "Running tests with ttl db and generic operator.\n");
  489. StringAppendOperatorTest::SetOpenDbFunction(&OpenTtlDb);
  490. result |= RUN_ALL_TESTS();
  491. }
  492. #endif // !ROCKSDB_LITE
  493. return result;
  494. }