YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/rocksdb/utilities/redis/redis_lists.cc
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2013 Facebook
2
//
3
// The following only applies to changes made to this file as part of YugaByte development.
4
//
5
// Portions Copyright (c) YugaByte, Inc.
6
//
7
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8
// in compliance with the License.  You may obtain a copy of the License at
9
//
10
// http://www.apache.org/licenses/LICENSE-2.0
11
//
12
// Unless required by applicable law or agreed to in writing, software distributed under the License
13
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14
// or implied.  See the License for the specific language governing permissions and limitations
15
// under the License.
16
//
17
/**
18
 * A (persistent) Redis API built using the rocksdb backend.
19
 * Implements Redis Lists as described on: http://redis.io/commands#list
20
 *
21
 * @throws All functions may throw a RedisListException on error/corruption.
22
 *
23
 * @notes Internally, the set of lists is stored in a rocksdb database,
24
 *        mapping keys to values. Each "value" is the list itself, storing
25
 *        some kind of internal representation of the data. All the
26
 *        representation details are handled by the RedisListIterator class.
27
 *        The present file should be oblivious to the representation details,
28
 *        handling only the client (Redis) API, and the calls to rocksdb.
29
 *
30
 * @TODO  Presently, all operations take at least O(NV) time where
31
 *        N is the number of elements in the list, and V is the average
32
 *        number of bytes per value in the list. So maybe, with merge operator
33
 *        we can improve this to an optimal O(V) amortized time, since we
34
 *        wouldn't have to read and re-write the entire list.
35
 *
36
 * @author Deon Nicholas (dnicholas@fb.com)
37
 */
38
39
#ifndef ROCKSDB_LITE
40
41
#include "yb/rocksdb/utilities/redis/redis_lists.h"
42
43
#include <iostream>
44
#include <memory>
45
46
#include "yb/rocksdb/util/coding.h"
47
48
#include "yb/util/slice.h"
49
#include "yb/util/status_log.h"
50
51
namespace rocksdb {
52
53
/// Constructors
54
55
RedisLists::RedisLists(const std::string& db_path,
56
                       Options options, bool destructive)
57
    : put_option_(),
58
12
      get_option_() {
59
60
  // Store the name of the database
61
12
  db_name_ = db_path;
62
63
  // If destructive, destroy the DB before re-opening it.
64
12
  if (destructive) {
65
10
    CHECK_OK(DestroyDB(db_name_, Options()));
66
10
  }
67
68
  // Now open and deal with the db
69
12
  DB* db;
70
12
  Status s = DB::Open(options, db_name_, &db);
71
12
  if (!s.ok()) {
72
0
    std::cerr << "ERROR " << s.ToString() << std::endl;
73
0
    assert(false);
74
0
  }
75
76
12
  db_ = std::unique_ptr<DB>(db);
77
12
}
78
79
80
/// Accessors
81
82
// Number of elements in the list associated with key
83
//   : throws RedisListException
84
88
int RedisLists::Length(const std::string& key) {
85
  // Extract the string data representing the list.
86
88
  std::string data = Get(key);
87
88
  // Return the length
89
88
  RedisListIterator it(data);
90
88
  return it.Length();
91
88
}
92
93
// Get the element at the specified index in the (list: key)
94
// Returns <empty> ("") on out-of-bounds
95
//   : throws RedisListException
96
bool RedisLists::Index(const std::string& key, int32_t index,
97
105
                       std::string* result) {
98
  // Extract the string data representing the list.
99
105
  std::string data = Get(key);
100
101
  // Handle REDIS negative indices (from the end); fast iff Length() takes O(1)
102
105
  if (index < 0) {
103
17
    index = Length(key) - (-index);  // replace (-i) with (N-i).
104
17
  }
105
106
  // Iterate through the list until the desired index is found.
107
105
  int curIndex = 0;
108
105
  RedisListIterator it(data);
109
447
  while(curIndex < index && !it.Done()) {
110
342
    ++curIndex;
111
342
    it.Skip();
112
342
  }
113
114
  // If we actually found the index
115
105
  if (curIndex == index && !it.Done()) {
116
96
    Slice elem;
117
96
    it.GetCurrent(&elem);
118
96
    if (result != NULL) {
119
96
      *result = elem.ToString();
120
96
    }
121
122
96
    return true;
123
9
  } else {
124
9
    return false;
125
9
  }
126
105
}
127
128
// Return a truncated version of the list.
129
// First, negative values for first/last are interpreted as "end of list".
130
// So, if first == -1, then it is re-set to index: (Length(key) - 1)
131
// Then, return exactly those indices i such that first <= i <= last.
132
//   : throws RedisListException
133
std::vector<std::string> RedisLists::Range(const std::string& key,
134
15
                                           int32_t first, int32_t last) {
135
  // Extract the string data representing the list.
136
15
  std::string data = Get(key);
137
138
  // Handle negative bounds (-1 means last element, etc.)
139
15
  int listLen = Length(key);
140
15
  if (first < 0) {
141
6
    first = listLen - (-first);           // Replace (-x) with (N-x)
142
6
  }
143
15
  if (last < 0) {
144
7
    last = listLen - (-last);
145
7
  }
146
147
  // Verify bounds (and truncate the range so that it is valid)
148
15
  first = std::max(first, 0);
149
15
  last = std::min(last, listLen-1);
150
15
  int len = std::max(last-first+1, 0);
151
152
  // Initialize the resulting list
153
15
  std::vector<std::string> result(len);
154
155
  // Traverse the list and update the vector
156
15
  int curIdx = 0;
157
15
  Slice elem;
158
73
  for (RedisListIterator it(data); !it.Done() && curIdx <= last; it.Skip()) {
159
58
    if (first <= curIdx && curIdx <= last) {
160
33
      it.GetCurrent(&elem);
161
33
      result[curIdx - first].assign(elem.cdata(), elem.size());
162
33
    }
163
164
58
    ++curIdx;
165
58
  }
166
167
  // Return the result. Might be empty
168
15
  return result;
169
15
}
170
171
// Print the (list: key) out to stdout. For debugging mostly. Public for now.
172
0
void RedisLists::Print(const std::string& key) {
173
  // Extract the string data representing the list.
174
0
  std::string data = Get(key);
175
176
  // Iterate through the list and print the items
177
0
  Slice elem;
178
0
  for (RedisListIterator it(data); !it.Done(); it.Skip()) {
179
0
    it.GetCurrent(&elem);
180
0
    std::cout << "ITEM " << elem.ToString() << std::endl;
181
0
  }
182
183
  // Now print the byte data
184
0
  RedisListIterator it(data);
185
0
  std::cout << "==Printing data==" << std::endl;
186
0
  std::cout << data.size() << std::endl;
187
0
  std::cout << it.Size() << " " << it.Length() << std::endl;
188
0
  Slice result = it.WriteResult();
189
0
  std::cout << result.data() << std::endl;
190
0
  if (true) {
191
0
    std::cout << "size: " << result.size() << std::endl;
192
0
    const char* val = result.cdata();
193
0
    for(size_t i = 0; i < result.size(); ++i) {
194
0
      std::cout << static_cast<int>(val[i]) << " " << (val[i] >= 32 ? val[i] : ' ') << std::endl;
195
0
    }
196
0
    std::cout << std::endl;
197
0
  }
198
0
}
199
200
/// Insert/Update Functions
201
/// Note: The "real" insert function is private. See below.
202
203
// InsertBefore and InsertAfter are simply wrappers around the Insert function.
204
int RedisLists::InsertBefore(const std::string& key, const std::string& pivot,
205
13
                             const std::string& value) {
206
13
  return Insert(key, pivot, value, false);
207
13
}
208
209
int RedisLists::InsertAfter(const std::string& key, const std::string& pivot,
210
19
                            const std::string& value) {
211
19
  return Insert(key, pivot, value, true);
212
19
}
213
214
349
std::string RedisLists::Get(const std::string& key) {
215
349
  std::string data;
216
349
  auto get_status = db_->Get(get_option_, key, &data);
217
349
  if (!get_status.ok() && !get_status.IsNotFound()) {
218
0
    CHECK_OK(get_status);
219
0
  }
220
349
  return data;
221
349
}
222
223
// Prepend value onto beginning of (list: key)
224
//   : throws RedisListException
225
39
int RedisLists::PushLeft(const std::string& key, const std::string& value) {
226
  // Get the original list data
227
39
  std::string data = Get(key);
228
229
  // Construct the result
230
39
  RedisListIterator it(data);
231
39
  it.Reserve(it.Size() + it.SizeOf(value));
232
39
  it.InsertElement(value);
233
234
  // Push the data back to the db and return the length
235
39
  CHECK_OK(db_->Put(put_option_, key, it.WriteResult()));
236
39
  return it.Length();
237
39
}
238
239
// Append value onto end of (list: key)
240
// TODO: Make this O(1) time. Might require MergeOperator.
241
//   : throws RedisListException
242
20
int RedisLists::PushRight(const std::string& key, const std::string& value) {
243
  // Get the original list data
244
20
  std::string data = Get(key);
245
246
  // Create an iterator to the data and seek to the end.
247
20
  RedisListIterator it(data);
248
20
  it.Reserve(it.Size() + it.SizeOf(value));
249
76
  while (!it.Done()) {
250
56
    it.Push();    // Write each element as we go
251
56
  }
252
253
  // Insert the new element at the current position (the end)
254
20
  it.InsertElement(value);
255
256
  // Push it back to the db, and return length
257
20
  CHECK_OK(db_->Put(put_option_, key, it.WriteResult()));
258
20
  return it.Length();
259
20
}
260
261
// Set (list: key)[idx] = val. Return true on success, false on fail.
262
//   : throws RedisListException
263
bool RedisLists::Set(const std::string& key, int32_t index,
264
29
                     const std::string& value) {
265
  // Get the original list data
266
29
  std::string data = Get(key);
267
268
  // Handle negative index for REDIS (meaning -index from end of list)
269
29
  if (index < 0) {
270
13
    index = Length(key) - (-index);
271
13
  }
272
273
  // Iterate through the list until we find the element we want
274
29
  int curIndex = 0;
275
29
  RedisListIterator it(data);
276
29
  it.Reserve(it.Size() + it.SizeOf(value));  // Over-estimate is fine
277
123
  while(curIndex < index && !it.Done()) {
278
94
    it.Push();
279
94
    ++curIndex;
280
94
  }
281
282
  // If not found, return false (this occurs when index was invalid)
283
29
  if (it.Done() || curIndex != index) {
284
10
    return false;
285
10
  }
286
287
  // Write the new element value, and drop the previous element value
288
19
  it.InsertElement(value);
289
19
  it.Skip();
290
291
  // Write the data to the database
292
  // Check status, since it needs to return true/false guarantee
293
19
  Status s = db_->Put(put_option_, key, it.WriteResult());
294
295
  // Success
296
19
  return s.ok();
297
19
}
298
299
/// Delete / Remove / Pop functions
300
301
// Trim (list: key) so that it will only contain the indices from start..stop
302
//  Invalid indices will not generate an error, just empty,
303
//  or the portion of the list that fits in this interval
304
//   : throws RedisListException
305
9
bool RedisLists::Trim(const std::string& key, int32_t start, int32_t stop) {
306
  // Get the original list data
307
9
  std::string data = Get(key);
308
309
  // Handle negative indices in REDIS
310
9
  int listLen = Length(key);
311
9
  if (start < 0) {
312
2
    start = listLen - (-start);
313
2
  }
314
9
  if (stop < 0) {
315
4
    stop = listLen - (-stop);
316
4
  }
317
318
  // Truncate bounds to only fit in the list
319
9
  start = std::max(start, 0);
320
9
  stop = std::min(stop, listLen-1);
321
322
  // Construct an iterator for the list. Drop all undesired elements.
323
9
  int curIndex = 0;
324
9
  RedisListIterator it(data);
325
9
  it.Reserve(it.Size());          // Over-estimate
326
41
  while(!it.Done()) {
327
    // If not within the range, just skip the item (drop it).
328
    // Otherwise, continue as usual.
329
32
    if (start <= curIndex && curIndex <= stop) {
330
20
      it.Push();
331
12
    } else {
332
12
      it.Skip();
333
12
    }
334
335
    // Increment the current index
336
32
    ++curIndex;
337
32
  }
338
339
  // Write the (possibly empty) result to the database
340
9
  Status s = db_->Put(put_option_, key, it.WriteResult());
341
342
  // Return true as long as the write succeeded
343
9
  return s.ok();
344
9
}
345
346
// Return and remove the first element in the list (or "" if empty)
347
//   : throws RedisListException
348
3
bool RedisLists::PopLeft(const std::string& key, std::string* result) {
349
  // Get the original list data
350
3
  std::string data = Get(key);
351
352
  // Point to first element in the list (if it exists), and get its value/size
353
3
  RedisListIterator it(data);
354
3
  if (it.Length() > 0) {            // Proceed only if list is non-empty
355
2
    Slice elem;
356
2
    it.GetCurrent(&elem);           // Store the value of the first element
357
2
    it.Reserve(it.Size() - it.SizeOf(elem));
358
2
    it.Skip();                      // DROP the first item and move to next
359
360
    // Update the db
361
2
    CHECK_OK(db_->Put(put_option_, key, it.WriteResult()));
362
363
    // Return the value
364
2
    if (result != NULL) {
365
2
      *result = elem.ToString();
366
2
    }
367
2
    return true;
368
1
  } else {
369
1
    return false;
370
1
  }
371
3
}
372
373
// Remove and return the last element in the list (or "" if empty)
374
// TODO: Make this O(1). Might require MergeOperator.
375
//   : throws RedisListException
376
2
bool RedisLists::PopRight(const std::string& key, std::string* result) {
377
  // Extract the original list data
378
2
  std::string data = Get(key);
379
380
  // Construct an iterator to the data and move to last element
381
2
  RedisListIterator it(data);
382
2
  it.Reserve(it.Size());
383
2
  int len = it.Length();
384
2
  int curIndex = 0;
385
6
  while(curIndex < (len-1) && !it.Done()) {
386
4
    it.Push();
387
4
    ++curIndex;
388
4
  }
389
390
  // Extract and drop/skip the last element
391
2
  if (curIndex == len-1) {
392
1
    assert(!it.Done());         // Sanity check. Should not have ended here.
393
394
    // Extract and pop the element
395
1
    Slice elem;
396
1
    it.GetCurrent(&elem);       // Save value of element.
397
1
    it.Skip();                  // Skip the element
398
399
    // Write the result to the database
400
1
    CHECK_OK(db_->Put(put_option_, key, it.WriteResult()));
401
402
    // Return the value
403
1
    if (result != NULL) {
404
1
      *result = elem.ToString();
405
1
    }
406
1
    return true;
407
1
  } else {
408
    // Must have been an empty list
409
1
    assert(it.Done() && len == 0 && curIndex == 0);
410
1
    return false;
411
1
  }
412
2
}
413
414
// Remove the (first or last) "num" occurrences of value in (list: key)
415
//   : throws RedisListException
416
int RedisLists::Remove(const std::string& key, int32_t num,
417
7
                       const std::string& value) {
418
  // Negative num ==> RemoveLast; Positive num ==> Remove First
419
7
  if (num < 0) {
420
3
    return RemoveLast(key, -num, value);
421
4
  } else if (num > 0) {
422
3
    return RemoveFirst(key, num, value);
423
1
  } else {
424
1
    return RemoveFirst(key, Length(key), value);
425
1
  }
426
7
}
427
428
// Remove the first "num" occurrences of value in (list: key).
429
//   : throws RedisListException
430
int RedisLists::RemoveFirst(const std::string& key, int32_t num,
431
4
                            const std::string& value) {
432
  // Ensure that the number is positive
433
4
  assert(num >= 0);
434
435
  // Extract the original list data
436
4
  std::string data = Get(key);
437
438
  // Traverse the list, appending all but the desired occurrences of value
439
4
  int numSkipped = 0;         // Keep track of the number of times value is seen
440
4
  Slice elem;
441
4
  RedisListIterator it(data);
442
4
  it.Reserve(it.Size());
443
22
  while (!it.Done()) {
444
18
    it.GetCurrent(&elem);
445
446
18
    if (elem == value && numSkipped < num) {
447
      // Drop this item if desired
448
3
      it.Skip();
449
3
      ++numSkipped;
450
15
    } else {
451
      // Otherwise keep the item and proceed as normal
452
15
      it.Push();
453
15
    }
454
18
  }
455
456
  // Put the result back to the database
457
4
  CHECK_OK(db_->Put(put_option_, key, it.WriteResult()));
458
459
  // Return the number of elements removed
460
4
  return numSkipped;
461
4
}
462
463
464
// Remove the last "num" occurrences of value in (list: key).
465
// TODO: I traverse the list 2x. Make faster. Might require MergeOperator.
466
//   : throws RedisListException
467
int RedisLists::RemoveLast(const std::string& key, int32_t num,
468
3
                           const std::string& value) {
469
  // Ensure that the number is positive
470
3
  assert(num >= 0);
471
472
  // Extract the original list data
473
3
  std::string data = Get(key);
474
475
  // Temporary variable to hold the "current element" in the blocks below
476
3
  Slice elem;
477
478
  // Count the total number of occurrences of value
479
3
  int totalOccs = 0;
480
36
  for (RedisListIterator it(data); !it.Done(); it.Skip()) {
481
33
    it.GetCurrent(&elem);
482
33
    if (elem == value) {
483
18
      ++totalOccs;
484
18
    }
485
33
  }
486
487
  // Construct an iterator to the data. Reserve enough space for the result.
488
3
  RedisListIterator it(data);
489
3
  int bytesRemoved = std::min(num, totalOccs) * it.SizeOf(value);
490
3
  it.Reserve(it.Size() - bytesRemoved);
491
492
  // Traverse the list, appending all but the desired occurrences of value.
493
  // Note: "Drop the last k occurrences" is equivalent to
494
  //  "keep only the first n-k occurrences", where n is total occurrences.
495
3
  int numKept = 0;          // Keep track of the number of times value is kept
496
36
  while(!it.Done()) {
497
33
    it.GetCurrent(&elem);
498
499
    // If we are within the deletion range and equal to value, drop it.
500
    // Otherwise, append/keep/push it.
501
33
    if (elem == value) {
502
18
      if (numKept < totalOccs - num) {
503
10
        it.Push();
504
10
        ++numKept;
505
8
      } else {
506
8
        it.Skip();
507
8
      }
508
15
    } else {
509
      // Always append the others
510
15
      it.Push();
511
15
    }
512
33
  }
513
514
  // Put the result back to the database
515
3
  CHECK_OK(db_->Put(put_option_, key, it.WriteResult()));
516
517
  // Return the number of elements removed
518
3
  return totalOccs - numKept;
519
3
}
520
521
/// Private functions
522
523
// Insert element value into (list: key), right before/after
524
//  the first occurrence of pivot
525
//   : throws RedisListException
526
int RedisLists::Insert(const std::string& key, const std::string& pivot,
527
32
                       const std::string& value, bool insert_after) {
528
  // Get the original list data
529
32
  std::string data = Get(key);
530
531
  // Construct an iterator to the data and reserve enough space for result.
532
32
  RedisListIterator it(data);
533
32
  it.Reserve(it.Size() + it.SizeOf(value));
534
535
  // Iterate through the list until we find the element we want
536
32
  Slice elem;
537
32
  bool found = false;
538
153
  while(!it.Done() && !found) {
539
121
    it.GetCurrent(&elem);
540
541
    // When we find the element, insert the element and mark found
542
121
    if (elem == pivot) {                // Found it!
543
25
      found = true;
544
25
      if (insert_after == true) {       // Skip one more, if inserting after it
545
15
        it.Push();
546
15
      }
547
25
      it.InsertElement(value);
548
96
    } else {
549
96
      it.Push();
550
96
    }
551
552
121
  }
553
554
  // Put the data (string) into the database
555
32
  if (found) {
556
25
    CHECK_OK(db_->Put(put_option_, key, it.WriteResult()));
557
25
  }
558
559
  // Returns the new (possibly unchanged) length of the list
560
32
  return it.Length();
561
32
}
562
563
}  // namespace rocksdb
564
#endif  // ROCKSDB_LITE