stringappend_test.cc 18 KB

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