leveldb-ruby 0.14 → 0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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();