Skip to content
This repository has been archived by the owner on Oct 20, 2024. It is now read-only.

Commit

Permalink
feat: new wrapper class for leveldb iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
yechentide committed Jul 24, 2023
1 parent d9eb52d commit 402b5ce
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 71 deletions.
10 changes: 2 additions & 8 deletions Sources/LvDBWrapper/LvDB.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,14 @@
#define LvDB_h

#import <Foundation/Foundation.h>
@class LvDBIterator;

@interface LvDB : NSObject

- (id)initWithDBPath:(NSString *)path;
- (void)close;

- (void)seekToFirst;
- (void)seekToLast;
- (void)seek:(NSData *)key;
- (void)next;
- (void)prev;
- (BOOL)valid;
- (NSData *)key;
- (NSData *)value;
- (LvDBIterator *)makeIterator;
- (NSArray *)iterate:(NSData *)start :(NSData *)end;

- (NSData *)get:(NSData *)key;
Expand Down
46 changes: 7 additions & 39 deletions Sources/LvDBWrapper/LvDB.mm
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "LvDB.h"
#import "LvDBIterator.h"

#import <iostream>
#import <memory>
Expand All @@ -13,7 +14,6 @@
@implementation LvDB

std::unique_ptr<leveldb::DB> db;
std::unique_ptr<leveldb::Iterator> iter;
leveldb::Options options;
leveldb::ReadOptions readOptions;
leveldb::WriteOptions writeOptions;
Expand All @@ -27,7 +27,7 @@ - (id)initWithDBPath:(NSString *)path {
options.compressors[0] = new leveldb::ZlibCompressorRaw(-1); //use the new raw-zip compressor to write (and read)
options.compressors[1] = new leveldb::ZlibCompressor(); //also setup the old, slower compressor for backwards compatibility. This will only be used to read old compressed blocks.
// options.block_size = 163840;

readOptions.decompress_allocator = new leveldb::DecompressAllocator();
// writeOptions = leveldb::WriteOptions();

Expand All @@ -39,8 +39,7 @@ - (id)initWithDBPath:(NSString *)path {
return nil;
}
db.reset(tmp);
iter.reset(db->NewIterator(readOptions));
std::cout << "[LvDBWrapper] LevelDB connected: " << dbPath << std::endl;
std::cout << "[LvDBWrapper] LevelDB opened: " << dbPath << std::endl;
}
return self;
}
Expand All @@ -50,51 +49,20 @@ - (void)dealloc {
}

- (void)close {
iter.reset();
db.reset();
delete options.filter_policy;
delete options.block_cache;
delete options.compressors[0];
delete options.compressors[1];
delete readOptions.decompress_allocator;
std::cout << "[LvDBWrapper] LevelDB disconnected." << std::endl;
std::cout << "[LvDBWrapper] LevelDB closed." << std::endl;
}

/* ---------- ---------- ---------- ---------- ---------- ---------- */

- (void)seekToFirst {
iter->SeekToFirst();
}

- (void)seekToLast {
iter->SeekToLast();
}

- (void)seek:(NSData *)key {
leveldb::Slice dbKey = leveldb::Slice((const char *)[key bytes], [key length]);
iter->Seek(dbKey);
}

- (void)next {
iter->Next();
}

- (void)prev {
iter->Prev();
}

- (BOOL)valid {
return iter->Valid();
}

- (NSData *)key {
leveldb::Slice key = iter->key();
return [[NSData alloc] initWithBytes:key.data() length:key.size()];
}

- (NSData *)value {
leveldb::Slice value = iter->value();
return [[NSData alloc] initWithBytes:value.data() length:value.size()];
- (LvDBIterator *)makeIterator {
auto dbIterator = db->NewIterator(readOptions);
return [[LvDBIterator alloc] initFromIterator:dbIterator];
}

/// Iterate through all keys and data that exist between the given keys.
Expand Down
20 changes: 20 additions & 0 deletions Sources/LvDBWrapper/LvDBIterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef Header_h
#define Header_h

@interface LvDBIterator : NSObject

- (id)initFromIterator:(void *)dbIterator;
- (void)destroy;

- (void)seekToFirst;
- (void)seekToLast;
- (void)seek:(NSData *)key;
- (void)next;
- (void)prev;
- (BOOL)valid;
- (NSData *)key;
- (NSData *)value;

@end

#endif /* Header_h */
65 changes: 65 additions & 0 deletions Sources/LvDBWrapper/LvDBIterator.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#import "LvDB.h"
#import "LvDBIterator.h"

#import <iostream>
#import <memory>
#import "leveldb/db.h"

@implementation LvDBIterator

std::unique_ptr<leveldb::Iterator> iterator;

- (id)initFromIterator:(void *)dbIterator {
if (self = [super init]) {
std::cout << "[LvDBWrapper] Iterator generated." << std::endl;
leveldb::Iterator* it = static_cast<leveldb::Iterator*>(dbIterator);
iterator.reset(it);
}
return self;
}

- (void)dealloc {
std::cout << "[LvDBWrapper] Iterator deallocated." << std::endl;
}

- (void)destroy {
std::cout << "[LvDBWrapper] Iterator destroyed." << std::endl;
iterator.reset();
}

- (void)seekToFirst {
iterator->SeekToFirst();
}

- (void)seekToLast {
iterator->SeekToLast();
}

- (void)seek:(NSData *)key {
leveldb::Slice dbKey = leveldb::Slice((const char *)[key bytes], [key length]);
iterator->Seek(dbKey);
}

- (void)next {
iterator->Next();
}

- (void)prev {
iterator->Prev();
}

- (BOOL)valid {
return iterator->Valid();
}

- (NSData *)key {
leveldb::Slice key = iterator->key();
return [[NSData alloc] initWithBytes:key.data() length:key.size()];
}

- (NSData *)value {
leveldb::Slice value = iterator->value();
return [[NSData alloc] initWithBytes:value.data() length:value.size()];
}

@end
1 change: 1 addition & 0 deletions Sources/LvDBWrapper/include/ExposedHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
#define ExposedHeader_h

#include "../LvDB.h"
#include "../LvDBIterator.h"

#endif /* ExposedHeader_h */
133 changes: 109 additions & 24 deletions Tests/LvDBWrapperTests/LvDBWrapperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,168 @@ import XCTest

final class LvDBWrapperTests: XCTestCase {
private let dbPath = Bundle.module.path(forResource: "world/db", ofType: nil)!
private func prepareTemporaryDB() -> String {
let originalURL: URL
let tempURL: URL
if #available(iOS 16.0, macOS 13.0, *) {
originalURL = URL(filePath: dbPath, directoryHint: .isDirectory)
tempURL = FileManager.default.temporaryDirectory.appending(component: "db", directoryHint: .isDirectory)
} else {
originalURL = URL(fileURLWithPath: dbPath, isDirectory: true)
tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("db", isDirectory: true)
}

do {
try? FileManager.default.removeItem(at: tempURL)
try FileManager.default.copyItem(at: originalURL, to: tempURL)
print("Created test db at \(tempURL)")
} catch {
XCTFail("Failed to copy the database from bundle.")
}

if #available(iOS 16.0, macOS 13.0, *) {
return tempURL.path()
} else {
return tempURL.path
}
}

func testOpenDB() {
let dbPath = prepareTemporaryDB()
defer {
print("\n")
}

print("\n")
guard let a = LvDB(dbPath: dbPath) else {
guard let db01 = LvDB(dbPath: dbPath),
let iterator01 = db01.makeIterator()
else {
XCTFail()
return
}
a.seek("mobevents".data(using: .utf8)!)
if a.valid(), let v = a.value() {
iterator01.seek("mobevents".data(using: .utf8)!)
if iterator01.valid(), let v = iterator01.value() {
XCTAssertEqual(126, v.count)
} else {
XCTFail()
}
a.close()
iterator01.destroy()
db01.close()

print("")
guard let b = LvDB(dbPath: dbPath) else {
guard let db02 = LvDB(dbPath: dbPath),
let iterator02 = db02.makeIterator()
else {
XCTFail()
return
}
b.seek("~local_player".data(using: .utf8)!)
if b.valid(), let v = b.value() {
iterator02.seek("~local_player".data(using: .utf8)!)
if iterator02.valid(), let v = iterator02.value() {
XCTAssertEqual(7323, v.count)
} else {
XCTFail()
}
b.close()
iterator02.destroy()
db02.close()

print("")
guard let c = LvDB(dbPath: dbPath) else {
guard let db03 = LvDB(dbPath: dbPath),
let iterator03 = db03.makeIterator()
else {
XCTFail()
return
}
c.seek("BiomeData".data(using: .utf8)!)
if c.valid(), let v = c.value() {
iterator03.seek("BiomeData".data(using: .utf8)!)
if iterator03.valid(), let v = iterator03.value() {
XCTAssertEqual(436, v.count)
} else {
XCTFail()
}
c.close()
iterator03.destroy()
db03.close()
}

func testSeek() {
guard let db = LvDB(dbPath: dbPath) else {
let dbPath = prepareTemporaryDB()
guard let db = LvDB(dbPath: dbPath),
let iterator = db.makeIterator()
else {
XCTFail()
return
}

var firstToLastCount = 0
db.seekToFirst()
while db.valid() {
iterator.seekToFirst()
while iterator.valid() {
firstToLastCount += 1
db.next()
iterator.next()
}

var lastToFirstCount = 0
db.seekToLast()
while db.valid() {
iterator.seekToLast()
while iterator.valid() {
lastToFirstCount += 1
db.prev()
iterator.prev()
}

XCTAssertEqual(firstToLastCount, lastToFirstCount)

let wellKnownKey = "~local_player".data(using: .utf8)!
db.seek(wellKnownKey)
guard let key = db.key(), let value = db.value() else {
iterator.seek(wellKnownKey)

guard let key = iterator.key(), let value = iterator.value() else {
XCTFail()
return
}
let keyStr = String(data: key, encoding: .utf8)
XCTAssertEqual("~local_player", keyStr ?? "")
XCTAssertEqual(7323, value.count)
}

func testDeleteKeysAndCountAgain() {
let dbPath = prepareTemporaryDB()
guard let db = LvDB(dbPath: dbPath),
let iterator01 = db.makeIterator()
else {
XCTFail()
return
}

var count01 = 0
iterator01.seekToFirst()
while iterator01.valid() {
count01 += 1
iterator01.next()
}

db.remove("mobevents".data(using: .utf8)!)
db.remove("~local_player".data(using: .utf8)!)
db.remove("BiomeData".data(using: .utf8)!)

var count02 = 0
iterator01.seekToFirst()
while iterator01.valid() {
count02 += 1
iterator01.next()
}

XCTAssertEqual(count01, count02)

guard let iterator02 = db.makeIterator() else {
XCTFail()
return
}
var count03 = 0
iterator02.seekToFirst()
while iterator02.valid() {
count03 += 1
iterator02.next()
}

XCTAssertEqual(count03, count01 - 3)

iterator01.destroy()
iterator02.destroy()
db.close()
}
}

0 comments on commit 402b5ce

Please sign in to comment.