leveldb-ruby 0.14 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/LICENSE +24 -0
  2. data/README +60 -16
  3. data/ext/leveldb/extconf.rb +1 -1
  4. data/ext/leveldb/leveldb.cc +187 -18
  5. data/leveldb/Makefile +82 -96
  6. data/leveldb/build_detect_platform +137 -51
  7. data/leveldb/db/c.cc +110 -0
  8. data/leveldb/db/db_bench.cc +105 -4
  9. data/leveldb/db/db_impl.cc +135 -45
  10. data/leveldb/db/db_impl.h +12 -10
  11. data/leveldb/db/db_test.cc +666 -431
  12. data/leveldb/db/dbformat.cc +20 -0
  13. data/leveldb/db/dbformat.h +12 -0
  14. data/leveldb/db/repair.cc +3 -1
  15. data/leveldb/db/skiplist.h +2 -1
  16. data/leveldb/db/table_cache.cc +42 -16
  17. data/leveldb/db/table_cache.h +11 -0
  18. data/leveldb/db/version_set.cc +46 -41
  19. data/leveldb/db/version_set.h +9 -0
  20. data/leveldb/db/write_batch.cc +13 -4
  21. data/leveldb/db/write_batch_internal.h +2 -0
  22. data/leveldb/db/write_batch_test.cc +31 -0
  23. data/leveldb/include/leveldb/c.h +29 -0
  24. data/leveldb/include/leveldb/db.h +2 -1
  25. data/leveldb/include/leveldb/filter_policy.h +70 -0
  26. data/leveldb/include/leveldb/options.h +8 -0
  27. data/leveldb/include/leveldb/status.h +6 -0
  28. data/leveldb/include/leveldb/table.h +15 -0
  29. data/leveldb/include/leveldb/table_builder.h +1 -0
  30. data/leveldb/port/atomic_pointer.h +13 -5
  31. data/leveldb/port/port.h +0 -2
  32. data/leveldb/port/port_example.h +10 -0
  33. data/leveldb/port/port_posix.cc +4 -0
  34. data/leveldb/port/port_posix.h +24 -9
  35. data/leveldb/table/block.cc +8 -4
  36. data/leveldb/table/block.h +3 -2
  37. data/leveldb/table/filter_block.cc +111 -0
  38. data/leveldb/table/filter_block.h +68 -0
  39. data/leveldb/table/filter_block_test.cc +128 -0
  40. data/leveldb/table/format.cc +17 -7
  41. data/leveldb/table/format.h +9 -4
  42. data/leveldb/table/table.cc +107 -6
  43. data/leveldb/table/table_builder.cc +49 -6
  44. data/leveldb/table/table_test.cc +8 -24
  45. data/leveldb/util/bloom.cc +95 -0
  46. data/leveldb/util/bloom_test.cc +159 -0
  47. data/leveldb/util/coding_test.cc +23 -0
  48. data/leveldb/util/comparator.cc +8 -3
  49. data/leveldb/util/env_posix.cc +46 -4
  50. data/leveldb/util/filter_policy.cc +11 -0
  51. data/leveldb/util/options.cc +2 -1
  52. data/lib/leveldb.rb +31 -5
  53. metadata +227 -109
  54. data/leveldb/port/port_android.cc +0 -64
  55. data/leveldb/port/port_android.h +0 -156
@@ -98,6 +98,26 @@ void InternalKeyComparator::FindShortSuccessor(std::string* key) const {
98
98
  }
99
99
  }
100
100
 
101
+ const char* InternalFilterPolicy::Name() const {
102
+ return user_policy_->Name();
103
+ }
104
+
105
+ void InternalFilterPolicy::CreateFilter(const Slice* keys, int n,
106
+ std::string* dst) const {
107
+ // We rely on the fact that the code in table.cc does not mind us
108
+ // adjusting keys[].
109
+ Slice* mkey = const_cast<Slice*>(keys);
110
+ for (int i = 0; i < n; i++) {
111
+ mkey[i] = ExtractUserKey(keys[i]);
112
+ // TODO(sanjay): Suppress dups?
113
+ }
114
+ user_policy_->CreateFilter(keys, n, dst);
115
+ }
116
+
117
+ bool InternalFilterPolicy::KeyMayMatch(const Slice& key, const Slice& f) const {
118
+ return user_policy_->KeyMayMatch(ExtractUserKey(key), f);
119
+ }
120
+
101
121
  LookupKey::LookupKey(const Slice& user_key, SequenceNumber s) {
102
122
  size_t usize = user_key.size();
103
123
  size_t needed = usize + 13; // A conservative estimate
@@ -8,6 +8,7 @@
8
8
  #include <stdio.h>
9
9
  #include "leveldb/comparator.h"
10
10
  #include "leveldb/db.h"
11
+ #include "leveldb/filter_policy.h"
11
12
  #include "leveldb/slice.h"
12
13
  #include "leveldb/table_builder.h"
13
14
  #include "util/coding.h"
@@ -123,6 +124,17 @@ class InternalKeyComparator : public Comparator {
123
124
  int Compare(const InternalKey& a, const InternalKey& b) const;
124
125
  };
125
126
 
127
+ // Filter policy wrapper that converts from internal keys to user keys
128
+ class InternalFilterPolicy : public FilterPolicy {
129
+ private:
130
+ const FilterPolicy* const user_policy_;
131
+ public:
132
+ explicit InternalFilterPolicy(const FilterPolicy* p) : user_policy_(p) { }
133
+ virtual const char* Name() const;
134
+ virtual void CreateFilter(const Slice* keys, int n, std::string* dst) const;
135
+ virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const;
136
+ };
137
+
126
138
  // Modules in this directory should keep internal keys wrapped inside
127
139
  // the following class instead of plain strings so that we do not
128
140
  // incorrectly use string comparisons instead of an InternalKeyComparator.
@@ -48,7 +48,8 @@ class Repairer {
48
48
  : dbname_(dbname),
49
49
  env_(options.env),
50
50
  icmp_(options.comparator),
51
- options_(SanitizeOptions(dbname, &icmp_, options)),
51
+ ipolicy_(options.filter_policy),
52
+ options_(SanitizeOptions(dbname, &icmp_, &ipolicy_, options)),
52
53
  owns_info_log_(options_.info_log != options.info_log),
53
54
  owns_cache_(options_.block_cache != options.block_cache),
54
55
  next_file_number_(1) {
@@ -99,6 +100,7 @@ class Repairer {
99
100
  std::string const dbname_;
100
101
  Env* const env_;
101
102
  InternalKeyComparator const icmp_;
103
+ InternalFilterPolicy const ipolicy_;
102
104
  Options const options_;
103
105
  bool owns_info_log_;
104
106
  bool owns_cache_;
@@ -105,7 +105,8 @@ class SkipList {
105
105
  port::AtomicPointer max_height_; // Height of the entire list
106
106
 
107
107
  inline int GetMaxHeight() const {
108
- return reinterpret_cast<intptr_t>(max_height_.NoBarrier_Load());
108
+ return static_cast<int>(
109
+ reinterpret_cast<intptr_t>(max_height_.NoBarrier_Load()));
109
110
  }
110
111
 
111
112
  // Read/written only by Insert().
@@ -42,23 +42,18 @@ TableCache::~TableCache() {
42
42
  delete cache_;
43
43
  }
44
44
 
45
- Iterator* TableCache::NewIterator(const ReadOptions& options,
46
- uint64_t file_number,
47
- uint64_t file_size,
48
- Table** tableptr) {
49
- if (tableptr != NULL) {
50
- *tableptr = NULL;
51
- }
52
-
45
+ Status TableCache::FindTable(uint64_t file_number, uint64_t file_size,
46
+ Cache::Handle** handle) {
47
+ Status s;
53
48
  char buf[sizeof(file_number)];
54
49
  EncodeFixed64(buf, file_number);
55
50
  Slice key(buf, sizeof(buf));
56
- Cache::Handle* handle = cache_->Lookup(key);
57
- if (handle == NULL) {
51
+ *handle = cache_->Lookup(key);
52
+ if (*handle == NULL) {
58
53
  std::string fname = TableFileName(dbname_, file_number);
59
54
  RandomAccessFile* file = NULL;
60
55
  Table* table = NULL;
61
- Status s = env_->NewRandomAccessFile(fname, &file);
56
+ s = env_->NewRandomAccessFile(fname, &file);
62
57
  if (s.ok()) {
63
58
  s = Table::Open(*options_, file, file_size, &table);
64
59
  }
@@ -68,13 +63,28 @@ Iterator* TableCache::NewIterator(const ReadOptions& options,
68
63
  delete file;
69
64
  // We do not cache error results so that if the error is transient,
70
65
  // or somebody repairs the file, we recover automatically.
71
- return NewErrorIterator(s);
66
+ } else {
67
+ TableAndFile* tf = new TableAndFile;
68
+ tf->file = file;
69
+ tf->table = table;
70
+ *handle = cache_->Insert(key, tf, 1, &DeleteEntry);
72
71
  }
72
+ }
73
+ return s;
74
+ }
73
75
 
74
- TableAndFile* tf = new TableAndFile;
75
- tf->file = file;
76
- tf->table = table;
77
- handle = cache_->Insert(key, tf, 1, &DeleteEntry);
76
+ Iterator* TableCache::NewIterator(const ReadOptions& options,
77
+ uint64_t file_number,
78
+ uint64_t file_size,
79
+ Table** tableptr) {
80
+ if (tableptr != NULL) {
81
+ *tableptr = NULL;
82
+ }
83
+
84
+ Cache::Handle* handle = NULL;
85
+ Status s = FindTable(file_number, file_size, &handle);
86
+ if (!s.ok()) {
87
+ return NewErrorIterator(s);
78
88
  }
79
89
 
80
90
  Table* table = reinterpret_cast<TableAndFile*>(cache_->Value(handle))->table;
@@ -86,6 +96,22 @@ Iterator* TableCache::NewIterator(const ReadOptions& options,
86
96
  return result;
87
97
  }
88
98
 
99
+ Status TableCache::Get(const ReadOptions& options,
100
+ uint64_t file_number,
101
+ uint64_t file_size,
102
+ const Slice& k,
103
+ void* arg,
104
+ void (*saver)(void*, const Slice&, const Slice&)) {
105
+ Cache::Handle* handle = NULL;
106
+ Status s = FindTable(file_number, file_size, &handle);
107
+ if (s.ok()) {
108
+ Table* t = reinterpret_cast<TableAndFile*>(cache_->Value(handle))->table;
109
+ s = t->InternalGet(options, k, arg, saver);
110
+ cache_->Release(handle);
111
+ }
112
+ return s;
113
+ }
114
+
89
115
  void TableCache::Evict(uint64_t file_number) {
90
116
  char buf[sizeof(file_number)];
91
117
  EncodeFixed64(buf, file_number);
@@ -35,6 +35,15 @@ class TableCache {
35
35
  uint64_t file_size,
36
36
  Table** tableptr = NULL);
37
37
 
38
+ // If a seek to internal key "k" in specified file finds an entry,
39
+ // call (*handle_result)(arg, found_key, found_value).
40
+ Status Get(const ReadOptions& options,
41
+ uint64_t file_number,
42
+ uint64_t file_size,
43
+ const Slice& k,
44
+ void* arg,
45
+ void (*handle_result)(void*, const Slice&, const Slice&));
46
+
38
47
  // Evict any entry for the specified file number
39
48
  void Evict(uint64_t file_number);
40
49
 
@@ -43,6 +52,8 @@ class TableCache {
43
52
  const std::string dbname_;
44
53
  const Options* options_;
45
54
  Cache* cache_;
55
+
56
+ Status FindTable(uint64_t file_number, uint64_t file_size, Cache::Handle**);
46
57
  };
47
58
 
48
59
  } // namespace leveldb
@@ -132,7 +132,7 @@ bool SomeFileOverlapsRange(
132
132
  const Comparator* ucmp = icmp.user_comparator();
133
133
  if (!disjoint_sorted_files) {
134
134
  // Need to check against all files
135
- for (int i = 0; i < files.size(); i++) {
135
+ for (size_t i = 0; i < files.size(); i++) {
136
136
  const FileMetaData* f = files[i];
137
137
  if (AfterFile(ucmp, smallest_user_key, f) ||
138
138
  BeforeFile(ucmp, largest_user_key, f)) {
@@ -255,35 +255,34 @@ void Version::AddIterators(const ReadOptions& options,
255
255
  }
256
256
  }
257
257
 
258
- // If "*iter" points at a value or deletion for user_key, store
259
- // either the value, or a NotFound error and return true.
260
- // Else return false.
261
- static bool GetValue(const Comparator* cmp,
262
- Iterator* iter, const Slice& user_key,
263
- std::string* value,
264
- Status* s) {
265
- if (!iter->Valid()) {
266
- return false;
267
- }
258
+ // Callback from TableCache::Get()
259
+ namespace {
260
+ enum SaverState {
261
+ kNotFound,
262
+ kFound,
263
+ kDeleted,
264
+ kCorrupt,
265
+ };
266
+ struct Saver {
267
+ SaverState state;
268
+ const Comparator* ucmp;
269
+ Slice user_key;
270
+ std::string* value;
271
+ };
272
+ }
273
+ static void SaveValue(void* arg, const Slice& ikey, const Slice& v) {
274
+ Saver* s = reinterpret_cast<Saver*>(arg);
268
275
  ParsedInternalKey parsed_key;
269
- if (!ParseInternalKey(iter->key(), &parsed_key)) {
270
- *s = Status::Corruption("corrupted key for ", user_key);
271
- return true;
272
- }
273
- if (cmp->Compare(parsed_key.user_key, user_key) != 0) {
274
- return false;
275
- }
276
- switch (parsed_key.type) {
277
- case kTypeDeletion:
278
- *s = Status::NotFound(Slice()); // Use an empty error message for speed
279
- break;
280
- case kTypeValue: {
281
- Slice v = iter->value();
282
- value->assign(v.data(), v.size());
283
- break;
276
+ if (!ParseInternalKey(ikey, &parsed_key)) {
277
+ s->state = kCorrupt;
278
+ } else {
279
+ if (s->ucmp->Compare(parsed_key.user_key, s->user_key) == 0) {
280
+ s->state = (parsed_key.type == kTypeValue) ? kFound : kDeleted;
281
+ if (s->state == kFound) {
282
+ s->value->assign(v.data(), v.size());
283
+ }
284
284
  }
285
285
  }
286
- return true;
287
286
  }
288
287
 
289
288
  static bool NewestFirst(FileMetaData* a, FileMetaData* b) {
@@ -361,21 +360,27 @@ Status Version::Get(const ReadOptions& options,
361
360
  last_file_read = f;
362
361
  last_file_read_level = level;
363
362
 
364
- Iterator* iter = vset_->table_cache_->NewIterator(
365
- options,
366
- f->number,
367
- f->file_size);
368
- iter->Seek(ikey);
369
- const bool done = GetValue(ucmp, iter, user_key, value, &s);
370
- if (!iter->status().ok()) {
371
- s = iter->status();
372
- delete iter;
363
+ Saver saver;
364
+ saver.state = kNotFound;
365
+ saver.ucmp = ucmp;
366
+ saver.user_key = user_key;
367
+ saver.value = value;
368
+ s = vset_->table_cache_->Get(options, f->number, f->file_size,
369
+ ikey, &saver, SaveValue);
370
+ if (!s.ok()) {
373
371
  return s;
374
- } else {
375
- delete iter;
376
- if (done) {
372
+ }
373
+ switch (saver.state) {
374
+ case kNotFound:
375
+ break; // Keep searching in other files
376
+ case kFound:
377
+ return s;
378
+ case kDeleted:
379
+ s = Status::NotFound(Slice()); // Use empty error message for speed
380
+ return s;
381
+ case kCorrupt:
382
+ s = Status::Corruption("corrupted key for ", user_key);
377
383
  return s;
378
- }
379
384
  }
380
385
  }
381
386
  }
@@ -1292,7 +1297,7 @@ Compaction* VersionSet::CompactRange(
1292
1297
  // Avoid compacting too much in one shot in case the range is large.
1293
1298
  const uint64_t limit = MaxFileSizeForLevel(level);
1294
1299
  uint64_t total = 0;
1295
- for (int i = 0; i < inputs.size(); i++) {
1300
+ for (size_t i = 0; i < inputs.size(); i++) {
1296
1301
  uint64_t s = inputs[i]->file_size;
1297
1302
  total += s;
1298
1303
  if (total >= limit) {
@@ -173,6 +173,15 @@ class VersionSet {
173
173
  // Allocate and return a new file number
174
174
  uint64_t NewFileNumber() { return next_file_number_++; }
175
175
 
176
+ // Arrange to reuse "file_number" unless a newer file number has
177
+ // already been allocated.
178
+ // REQUIRES: "file_number" was returned by a call to NewFileNumber().
179
+ void ReuseFileNumber(uint64_t file_number) {
180
+ if (next_file_number_ == file_number + 1) {
181
+ next_file_number_ = file_number;
182
+ }
183
+ }
184
+
176
185
  // Return the number of Table files at the specified level.
177
186
  int NumLevelFiles(int level) const;
178
187
 
@@ -23,6 +23,9 @@
23
23
 
24
24
  namespace leveldb {
25
25
 
26
+ // WriteBatch header has an 8-byte sequence number followed by a 4-byte count.
27
+ static const size_t kHeader = 12;
28
+
26
29
  WriteBatch::WriteBatch() {
27
30
  Clear();
28
31
  }
@@ -33,16 +36,16 @@ WriteBatch::Handler::~Handler() { }
33
36
 
34
37
  void WriteBatch::Clear() {
35
38
  rep_.clear();
36
- rep_.resize(12);
39
+ rep_.resize(kHeader);
37
40
  }
38
41
 
39
42
  Status WriteBatch::Iterate(Handler* handler) const {
40
43
  Slice input(rep_);
41
- if (input.size() < 12) {
44
+ if (input.size() < kHeader) {
42
45
  return Status::Corruption("malformed WriteBatch (too small)");
43
46
  }
44
47
 
45
- input.remove_prefix(12);
48
+ input.remove_prefix(kHeader);
46
49
  Slice key, value;
47
50
  int found = 0;
48
51
  while (!input.empty()) {
@@ -131,8 +134,14 @@ Status WriteBatchInternal::InsertInto(const WriteBatch* b,
131
134
  }
132
135
 
133
136
  void WriteBatchInternal::SetContents(WriteBatch* b, const Slice& contents) {
134
- assert(contents.size() >= 12);
137
+ assert(contents.size() >= kHeader);
135
138
  b->rep_.assign(contents.data(), contents.size());
136
139
  }
137
140
 
141
+ void WriteBatchInternal::Append(WriteBatch* dst, const WriteBatch* src) {
142
+ SetCount(dst, Count(dst) + Count(src));
143
+ assert(src->rep_.size() >= kHeader);
144
+ dst->rep_.append(src->rep_.data() + kHeader, src->rep_.size() - kHeader);
145
+ }
146
+
138
147
  } // namespace leveldb
@@ -39,6 +39,8 @@ class WriteBatchInternal {
39
39
  static void SetContents(WriteBatch* batch, const Slice& contents);
40
40
 
41
41
  static Status InsertInto(const WriteBatch* batch, MemTable* memtable);
42
+
43
+ static void Append(WriteBatch* dst, const WriteBatch* src);
42
44
  };
43
45
 
44
46
  } // namespace leveldb
@@ -18,6 +18,7 @@ static std::string PrintContents(WriteBatch* b) {
18
18
  mem->Ref();
19
19
  std::string state;
20
20
  Status s = WriteBatchInternal::InsertInto(b, mem);
21
+ int count = 0;
21
22
  Iterator* iter = mem->NewIterator();
22
23
  for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
23
24
  ParsedInternalKey ikey;
@@ -29,11 +30,13 @@ static std::string PrintContents(WriteBatch* b) {
29
30
  state.append(", ");
30
31
  state.append(iter->value().ToString());
31
32
  state.append(")");
33
+ count++;
32
34
  break;
33
35
  case kTypeDeletion:
34
36
  state.append("Delete(");
35
37
  state.append(ikey.user_key.ToString());
36
38
  state.append(")");
39
+ count++;
37
40
  break;
38
41
  }
39
42
  state.append("@");
@@ -42,6 +45,8 @@ static std::string PrintContents(WriteBatch* b) {
42
45
  delete iter;
43
46
  if (!s.ok()) {
44
47
  state.append("ParseError()");
48
+ } else if (count != WriteBatchInternal::Count(b)) {
49
+ state.append("CountMismatch()");
45
50
  }
46
51
  mem->Unref();
47
52
  return state;
@@ -82,6 +87,32 @@ TEST(WriteBatchTest, Corruption) {
82
87
  PrintContents(&batch));
83
88
  }
84
89
 
90
+ TEST(WriteBatchTest, Append) {
91
+ WriteBatch b1, b2;
92
+ WriteBatchInternal::SetSequence(&b1, 200);
93
+ WriteBatchInternal::SetSequence(&b2, 300);
94
+ WriteBatchInternal::Append(&b1, &b2);
95
+ ASSERT_EQ("",
96
+ PrintContents(&b1));
97
+ b2.Put("a", "va");
98
+ WriteBatchInternal::Append(&b1, &b2);
99
+ ASSERT_EQ("Put(a, va)@200",
100
+ PrintContents(&b1));
101
+ b2.Clear();
102
+ b2.Put("b", "vb");
103
+ WriteBatchInternal::Append(&b1, &b2);
104
+ ASSERT_EQ("Put(a, va)@200"
105
+ "Put(b, vb)@201",
106
+ PrintContents(&b1));
107
+ b2.Delete("foo");
108
+ WriteBatchInternal::Append(&b1, &b2);
109
+ ASSERT_EQ("Put(a, va)@200"
110
+ "Put(b, vb)@202"
111
+ "Put(b, vb)@201"
112
+ "Delete(foo)@203",
113
+ PrintContents(&b1));
114
+ }
115
+
85
116
  } // namespace leveldb
86
117
 
87
118
  int main(int argc, char** argv) {
@@ -55,6 +55,7 @@ typedef struct leveldb_cache_t leveldb_cache_t;
55
55
  typedef struct leveldb_comparator_t leveldb_comparator_t;
56
56
  typedef struct leveldb_env_t leveldb_env_t;
57
57
  typedef struct leveldb_filelock_t leveldb_filelock_t;
58
+ typedef struct leveldb_filterpolicy_t leveldb_filterpolicy_t;
58
59
  typedef struct leveldb_iterator_t leveldb_iterator_t;
59
60
  typedef struct leveldb_logger_t leveldb_logger_t;
60
61
  typedef struct leveldb_options_t leveldb_options_t;
@@ -127,6 +128,11 @@ extern void leveldb_approximate_sizes(
127
128
  const char* const* range_limit_key, const size_t* range_limit_key_len,
128
129
  uint64_t* sizes);
129
130
 
131
+ extern void leveldb_compact_range(
132
+ leveldb_t* db,
133
+ const char* start_key, size_t start_key_len,
134
+ const char* limit_key, size_t limit_key_len);
135
+
130
136
  /* Management operations */
131
137
 
132
138
  extern void leveldb_destroy_db(
@@ -177,6 +183,9 @@ extern void leveldb_options_destroy(leveldb_options_t*);
177
183
  extern void leveldb_options_set_comparator(
178
184
  leveldb_options_t*,
179
185
  leveldb_comparator_t*);
186
+ extern void leveldb_options_set_filter_policy(
187
+ leveldb_options_t*,
188
+ leveldb_filterpolicy_t*);
180
189
  extern void leveldb_options_set_create_if_missing(
181
190
  leveldb_options_t*, unsigned char);
182
191
  extern void leveldb_options_set_error_if_exists(
@@ -209,6 +218,26 @@ extern leveldb_comparator_t* leveldb_comparator_create(
209
218
  const char* (*name)(void*));
210
219
  extern void leveldb_comparator_destroy(leveldb_comparator_t*);
211
220
 
221
+ /* Filter policy */
222
+
223
+ extern leveldb_filterpolicy_t* leveldb_filterpolicy_create(
224
+ void* state,
225
+ void (*destructor)(void*),
226
+ char* (*create_filter)(
227
+ void*,
228
+ const char* const* key_array, const size_t* key_length_array,
229
+ int num_keys,
230
+ size_t* filter_length),
231
+ unsigned char (*key_may_match)(
232
+ void*,
233
+ const char* key, size_t length,
234
+ const char* filter, size_t filter_length),
235
+ const char* (*name)(void*));
236
+ extern void leveldb_filterpolicy_destroy(leveldb_filterpolicy_t*);
237
+
238
+ extern leveldb_filterpolicy_t* leveldb_filterpolicy_create_bloom(
239
+ int bits_per_key);
240
+
212
241
  /* Read options */
213
242
 
214
243
  extern leveldb_readoptions_t* leveldb_readoptions_create();