From 4e11bf7d470dca7defdf349b62cdf1ebf6850551 Mon Sep 17 00:00:00 2001 From: ened Date: Wed, 7 Jun 2023 09:10:18 +0900 Subject: [PATCH] Implement delete, select functions --- KissMeme.xcodeproj/project.pbxproj | 4 + KissMeme/CString.swift | 33 +++++++ KissMeme/KissDB.swift | 154 ++++++++++++++++++++--------- KissMemeTests/KissMemeTests.swift | 37 ++++--- kiss-db/kiss-db/kiss-db.c | 60 +++++++---- kiss-db/kiss-db/kiss-db.h | 16 +-- 6 files changed, 219 insertions(+), 85 deletions(-) create mode 100644 KissMeme/CString.swift diff --git a/KissMeme.xcodeproj/project.pbxproj b/KissMeme.xcodeproj/project.pbxproj index 435169a..fbf20ab 100644 --- a/KissMeme.xcodeproj/project.pbxproj +++ b/KissMeme.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 348168332A2EC3AC00A50BD3 /* libkiss-db.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 348168302A2EC26F00A50BD3 /* libkiss-db.a */; }; 348168412A2EE0C600A50BD3 /* KissMemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348168402A2EE0C600A50BD3 /* KissMemeTests.swift */; }; 348168422A2EE0C600A50BD3 /* KissMeme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349942482A217D85009457A4 /* KissMeme.framework */; }; + 3481684B2A2F961500A50BD3 /* CString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3481684A2A2F961500A50BD3 /* CString.swift */; }; 3499424D2A217D85009457A4 /* KissMeme.docc in Sources */ = {isa = PBXBuildFile; fileRef = 3499424C2A217D85009457A4 /* KissMeme.docc */; }; 349942592A217D85009457A4 /* KissMeme.h in Headers */ = {isa = PBXBuildFile; fileRef = 3499424B2A217D85009457A4 /* KissMeme.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -41,6 +42,7 @@ 348168312A2EC2A200A50BD3 /* kiss-db.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "kiss-db.h"; path = "kiss-db/kiss-db/kiss-db.h"; sourceTree = SOURCE_ROOT; }; 3481683E2A2EE0C600A50BD3 /* KissMemeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KissMemeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 348168402A2EE0C600A50BD3 /* KissMemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissMemeTests.swift; sourceTree = ""; }; + 3481684A2A2F961500A50BD3 /* CString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CString.swift; sourceTree = ""; }; 349942482A217D85009457A4 /* KissMeme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KissMeme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3499424B2A217D85009457A4 /* KissMeme.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KissMeme.h; sourceTree = ""; }; 3499424C2A217D85009457A4 /* KissMeme.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = KissMeme.docc; sourceTree = ""; }; @@ -110,6 +112,7 @@ 3499424C2A217D85009457A4 /* KissMeme.docc */, 348168252A2E478F00A50BD3 /* KissMeme.swift */, 348168272A2E4B7700A50BD3 /* KissDB.swift */, + 3481684A2A2F961500A50BD3 /* CString.swift */, ); path = KissMeme; sourceTree = ""; @@ -258,6 +261,7 @@ files = ( 3499424D2A217D85009457A4 /* KissMeme.docc in Sources */, 348168282A2E4B7700A50BD3 /* KissDB.swift in Sources */, + 3481684B2A2F961500A50BD3 /* CString.swift in Sources */, 348168262A2E478F00A50BD3 /* KissMeme.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/KissMeme/CString.swift b/KissMeme/CString.swift new file mode 100644 index 0000000..bbbca21 --- /dev/null +++ b/KissMeme/CString.swift @@ -0,0 +1,33 @@ +// +// CString.swift +// KissMeme +// +// Created by ened-book-m1 on 2023/06/07. +// + +import Foundation + + +public class CString { + + public let mutableString: UnsafeMutablePointer + + public var cstring: UnsafePointer { + UnsafePointer(mutableString) + } + + public let size: Int + + public init(_ string: String) { + let count = string.utf8.count + 1 + mutableString = UnsafeMutablePointer.allocate(capacity: count) + size = count + string.withCString { (baseAddress) in + mutableString.initialize(from: baseAddress, count: count) + } + } + + deinit { + mutableString.deallocate() + } +} diff --git a/KissMeme/KissDB.swift b/KissMeme/KissDB.swift index 2ed52b4..ac1b8fc 100644 --- a/KissMeme/KissDB.swift +++ b/KissMeme/KissDB.swift @@ -15,8 +15,18 @@ public enum KissDBError: Error { public class KissDB: NSObject { - let cPtr: OpaquePointer - let cError: UnsafeMutablePointer! + public struct Item { + public let key: String + public let value: String + + public init(key: String, value: String) { + self.key = key + self.value = value + } + } + + private let cPtr: OpaquePointer + private let cError: UnsafeMutablePointer! public init(directory: URL) throws { let cConfig = UnsafeMutablePointer.allocate(capacity: 1) @@ -35,7 +45,7 @@ public class KissDB: NSObject { } public var count: Int64 { - Int64(kiss_db_count(cPtr, cError)) + Int64(kiss_db_count(cPtr)) } deinit { @@ -61,38 +71,112 @@ public class KissDB: NSObject { } } - public func add(key: String, value: String) throws -> Bool { - let item = UnsafeMutablePointer.allocate(capacity: 1) + public func insert(item: Item) throws { + let cItem = UnsafeMutablePointer.allocate(capacity: 1) defer { - item.deallocate() + cItem.deallocate() } - let key = CString(key) - let value = CString(value) + let key = CString(item.key) + let value = CString(item.value) - item.pointee.key = UnsafeMutableRawPointer(key.mutableString) - item.pointee.key_size = UInt32(key.size) - item.pointee.data = UnsafeMutableRawPointer(key.mutableString) - item.pointee.data_size = UInt64(value.size) + cItem.pointee.key = UnsafeMutableRawPointer(key.mutableString) + cItem.pointee.key_size = UInt32(key.size) + cItem.pointee.data = UnsafeMutableRawPointer(key.mutableString) + cItem.pointee.data_size = UInt64(value.size) - guard kiss_db_insert(cPtr, item, true, cError) else { + guard kiss_db_insert(cPtr, cItem, true, cError) else { throw KissDBError.general(cError.code, cError.message) } + } + + public func insertBulk(from: ((Item)->Bool)?) throws { + let obj = CallbackObject(itemCallback: from) + let userPtr = Unmanaged.passUnretained(obj).toOpaque() + + guard kiss_db_insert_bulk(cPtr, true, CallbackObject.insertCallback, userPtr, cError) else { + throw KissDBError.general(cError.code, cError.message) + } + } + + public func delete(key: String, output: ((Item)->Bool)?) throws { + let key = CString(key) + + let obj = CallbackObject(itemCallback: output) + let userPtr = Unmanaged.passUnretained(obj).toOpaque() + guard kiss_db_delete(cPtr, key.mutableString, UInt32(key.size), CallbackObject.deleteCallback, userPtr, cError) else { + throw KissDBError.general(cError.code, cError.message) + } + } + + public func select(key: String, output: ((Item)->Bool)?) throws { + let key = CString(key) + + let obj = CallbackObject(itemCallback: output) + let userPtr = Unmanaged.passUnretained(obj).toOpaque() + guard kiss_db_select(cPtr, key.mutableString, UInt32(key.size), CallbackObject.selectCallback, userPtr, cError) else { + throw KissDBError.general(cError.code, cError.message) + } + } + + public func selectAll(output: ((Item)->Bool)?) throws { + let obj = CallbackObject(itemCallback: output) + let userPtr = Unmanaged.passUnretained(obj).toOpaque() + guard kiss_db_select_bulk(cPtr, CallbackObject.selectCallback, userPtr, cError) else { + throw KissDBError.general(cError.code, cError.message) + } + } +} + + +class CallbackObject: NSObject { + + let itemCallback: ((KissDB.Item)->Bool)? + + init(itemCallback: ((KissDB.Item)->Bool)?) { + self.itemCallback = itemCallback + } + + static let insertCallback: kiss_insert_callback = { (item: UnsafeMutablePointer?, userPtr: UnsafeMutableRawPointer?) -> kiss_bool in + let obj = Unmanaged.fromOpaque(userPtr!).takeUnretainedValue() + if let itemCallback = obj.itemCallback, let item = item { + let cKey = item.pointee.key.bindMemory(to: CChar.self, capacity: Int(item.pointee.key_size)) + let cValue = item.pointee.data.bindMemory(to: CChar.self, capacity: Int(item.pointee.data_size)) + let key = String(cString: cKey) + let value = String(cString: cValue) + let item = KissDB.Item(key: key, value: value) + return itemCallback(item) + } + // Continue to insert remained items return true } - public func remove(key: String) throws -> Bool { -// -// let key = CString(key) -// -// guard kiss_db_delete(cPtr, key.mutableString, UInt32(key.size), KissDB.deleteCallback, cError) else { -// throw KissDBError.general(cError.code, cError.message) -// } + static let deleteCallback: kiss_delete_callback = { (item: UnsafeMutablePointer!, userPtr: UnsafeMutableRawPointer?) -> kiss_bool in + let obj = Unmanaged.fromOpaque(userPtr!).takeUnretainedValue() + if let itemCallback = obj.itemCallback, let item = item { + let cKey = item.pointee.key.bindMemory(to: CChar.self, capacity: Int(item.pointee.key_size)) + let cValue = item.pointee.data.bindMemory(to: CChar.self, capacity: Int(item.pointee.data_size)) + let key = String(cString: cKey) + let value = String(cString: cValue) + let item = KissDB.Item(key: key, value: value) + return itemCallback(item) + } + // Continue to delete remained items return true } -// static func deleteCallback(UnsafeMutablePointer?) -> kiss_bool = { item in -// return true -// } + static let selectCallback: kiss_select_callback = { (item: UnsafeMutablePointer!, userPtr: UnsafeMutableRawPointer?) -> kiss_bool in + let obj = Unmanaged.fromOpaque(userPtr!).takeUnretainedValue() + if let itemCallback = obj.itemCallback, let item = item { + let cKey = item.pointee.key.bindMemory(to: CChar.self, capacity: Int(item.pointee.key_size)) + let cValue = item.pointee.data.bindMemory(to: CChar.self, capacity: Int(item.pointee.data_size)) + let key = String(cString: cKey) + let value = String(cString: cValue) + let item = KissDB.Item(key: key, value: value) + return itemCallback(item) + } + // Continue to select remained items + return true + } } @@ -107,26 +191,8 @@ extension UnsafeMutablePointer where Pointee == kiss_db_error_t { } -public class CString { - - public let mutableString: UnsafeMutablePointer - - public var cstring: UnsafePointer { - UnsafePointer(mutableString) - } - - public let size: Int - - public init(_ string: String) { - let count = string.utf8.count - mutableString = UnsafeMutablePointer.allocate(capacity: count) - size = count - string.withCString { (baseAddress) in - mutableString.initialize(from: baseAddress, count: count) - } - } - - deinit { - mutableString.deallocate() +extension KissDB { + public static var appTime: TimeInterval { + ProcessInfo.processInfo.systemUptime } } diff --git a/KissMemeTests/KissMemeTests.swift b/KissMemeTests/KissMemeTests.swift index 56a8f15..21a64d1 100644 --- a/KissMemeTests/KissMemeTests.swift +++ b/KissMemeTests/KissMemeTests.swift @@ -26,22 +26,35 @@ final class KissMemeTests: XCTestCase { // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. let directory = URL.currentDirectory().appending(path: "testdb") + try? FileManager.default.removeItem(at: directory) try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) - print(directory.path) - do { - let db = try KissDB(directory: directory) - print("DB count: \(db.count)") - - try db.begin() - _ = try db.add(key: "hello", value: "world") - try db.commit() - - print("DB count: \(db.count)") - } catch { - print(error) + var startTime: TimeInterval, endTime: TimeInterval + + let db = try KissDB(directory: directory) + + startTime = KissDB.appTime + try db.begin() + print("DB count: \(db.count)") + for i in 0 ..< 1_000_000 { + try db.insert(item: KissDB.Item(key: "hello\(i)", value: "world\(i)")) } + try db.commit() + endTime = KissDB.appTime + print("DB count: \(db.count) insert elapsed: \(endTime - startTime)") + + startTime = KissDB.appTime + try db.begin() + for i in 0 ..< 1_000_000 { + try db.delete(key: "hello\(i)", output: { item in + print("\(item.value)") + return true + }) + } + try db.commit() + endTime = KissDB.appTime + print("DB count: \(db.count) delete elapsed: \(endTime - startTime)") } func testPerformanceExample() throws { diff --git a/kiss-db/kiss-db/kiss-db.c b/kiss-db/kiss-db/kiss-db.c index dc45b40..f588c73 100644 --- a/kiss-db/kiss-db/kiss-db.c +++ b/kiss-db/kiss-db/kiss-db.c @@ -23,15 +23,23 @@ return val; \ } +#define CHECK_RET(expr, val) \ + if ((rc = (expr)) != MDB_SUCCESS) { \ + return val; \ + } + #define GIGA_BYTE_SIZE (1024UL * 1024 * 1024) #define TERA_BYTE_SIZE (1024UL * GIGA_BYTE_SIZE) +static kiss_uint64 _kiss_db_update_count(kiss_db_ptr ptr); + typedef struct kiss_db { MDB_env* env; MDB_dbi dbi; MDB_txn* txn; MDB_cursor* cursor; + kiss_uint64 count; } kiss_db_t; @@ -58,7 +66,6 @@ void kiss_db_destroy(kiss_db_ptr ptr) return; } mdb_cursor_close(ptr->cursor); - mdb_txn_abort(ptr->txn); mdb_dbi_close(ptr->env, ptr->dbi); mdb_env_close(ptr->env); free(ptr); @@ -72,22 +79,24 @@ kiss_bool kiss_db_begin(kiss_db_ptr ptr, kiss_db_error_t* error) CHECK_RET_ERROR(mdb_txn_begin(ptr->env, NULL, 0, &ptr->txn), false); CHECK_RET_ERROR(mdb_dbi_open(ptr->txn, NULL, MDB_CREATE|MDB_DUPSORT, &ptr->dbi), false); + _kiss_db_update_count(ptr); return true; } kiss_bool kiss_db_commit(kiss_db_ptr ptr, kiss_db_error_t* error) { + _kiss_db_update_count(ptr); mdb_txn_commit(ptr->txn); - return true; } kiss_bool kiss_db_rollback(kiss_db_ptr ptr, kiss_db_error_t* error) { + _kiss_db_update_count(ptr); mdb_txn_abort(ptr->txn); - + return true; } @@ -99,31 +108,36 @@ kiss_bool kiss_db_insert(kiss_db_ptr ptr, kiss_db_item_t* item, kiss_bool update MDB_val key, data; - if (update) { + if (update == false) { flags |= MDB_NOOVERWRITE; } + key.mv_data = item->key; + key.mv_size = item->key_size; + data.mv_data = item->data; + data.mv_size = item->data_size; + CHECK_RET_ERROR(mdb_put(ptr->txn, ptr->dbi, &key, &data, flags), false); return true; } -kiss_bool kiss_db_insert_bulk(kiss_db_ptr ptr, kiss_bool update, kiss_insert_callback callback, kiss_db_error_t* error) +kiss_bool kiss_db_insert_bulk(kiss_db_ptr ptr, kiss_bool update, kiss_insert_callback callback, void* callback_user_ptr, kiss_db_error_t* error) { int rc = 0; unsigned int flags = 0; MDB_val key, data; - if (update) { + if (update == false) { flags |= MDB_NOOVERWRITE; } kiss_db_item_t item; while (true) { - if (false == callback(&item)) { + if (false == callback(&item, callback_user_ptr)) { break; } key.mv_data = item.key; @@ -138,7 +152,7 @@ kiss_bool kiss_db_insert_bulk(kiss_db_ptr ptr, kiss_bool update, kiss_insert_cal } -kiss_bool kiss_db_delete(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_size, kiss_delete_callback callback, kiss_db_error_t* error) +kiss_bool kiss_db_delete(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_size, kiss_delete_callback callback, void* callback_user_ptr, kiss_db_error_t* error) { int rc = 0; MDB_val key, data; @@ -148,9 +162,6 @@ kiss_bool kiss_db_delete(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_s kiss_db_item_t item; - item.key = key.mv_data; - item.key_size = key.mv_size; - while (true) { rc = mdb_del(ptr->txn, ptr->dbi, &key, &data); if (rc == MDB_NOTFOUND) { @@ -161,7 +172,7 @@ kiss_bool kiss_db_delete(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_s item.data = data.mv_data; item.data_size = data.mv_size; - if (false == callback(&item)) { + if (false == callback(&item, callback_user_ptr)) { break; } } @@ -169,7 +180,7 @@ kiss_bool kiss_db_delete(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_s } -kiss_bool kiss_db_select(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_size, kiss_select_callback callback, kiss_db_error_t* error) +kiss_bool kiss_db_select(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_size, kiss_select_callback callback, void* callback_user_ptr, kiss_db_error_t* error) { int rc = 0; MDB_val key, data; @@ -183,11 +194,11 @@ kiss_bool kiss_db_select(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_s while ((rc = mdb_cursor_get(ptr->cursor, &key, &data, MDB_NEXT_DUP)) == 0) { item.key = key.mv_data; - item.key_size = key.mv_size; + item.key_size = (kiss_uint)key.mv_size; item.data = data.mv_data; item.data_size = data.mv_size; - callback(&item); + callback(&item, callback_user_ptr); } mdb_cursor_close(ptr->cursor); @@ -195,7 +206,7 @@ kiss_bool kiss_db_select(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_s } -kiss_bool kiss_db_select_bulk(kiss_db_ptr ptr, kiss_select_callback callback, kiss_db_error_t* error) +kiss_bool kiss_db_select_bulk(kiss_db_ptr ptr, kiss_select_callback callback, void* callback_user_ptr, kiss_db_error_t* error) { int rc = 0; MDB_val key, data; @@ -206,11 +217,11 @@ kiss_bool kiss_db_select_bulk(kiss_db_ptr ptr, kiss_select_callback callback, ki while ((rc = mdb_cursor_get(ptr->cursor, &key, &data, MDB_NEXT)) == 0) { item.key = key.mv_data; - item.key_size = key.mv_size; + item.key_size = (kiss_uint)key.mv_size; item.data = data.mv_data; item.data_size = data.mv_size; - callback(&item); + callback(&item, callback_user_ptr); } mdb_cursor_close(ptr->cursor); @@ -218,12 +229,19 @@ kiss_bool kiss_db_select_bulk(kiss_db_ptr ptr, kiss_select_callback callback, ki } -kiss_int64 kiss_db_count(kiss_db_ptr ptr, kiss_db_error_t* error) +kiss_int64 kiss_db_count(kiss_db_ptr ptr) +{ + return ptr->count; +} + + +static kiss_uint64 _kiss_db_update_count(kiss_db_ptr ptr) { int rc = 0; MDB_stat stat; - CHECK_RET_ERROR(mdb_stat(ptr->txn, ptr->dbi, &stat), 0); + CHECK_RET(mdb_stat(ptr->txn, ptr->dbi, &stat), 0); - return stat.ms_entries; + ptr->count = stat.ms_entries; + return ptr->count; } diff --git a/kiss-db/kiss-db/kiss-db.h b/kiss-db/kiss-db/kiss-db.h index f14495a..db70ebe 100644 --- a/kiss-db/kiss-db/kiss-db.h +++ b/kiss-db/kiss-db/kiss-db.h @@ -52,9 +52,9 @@ extern "C" #endif -typedef kiss_bool (*kiss_insert_callback)(kiss_db_item_t* item); -typedef kiss_bool (*kiss_delete_callback)(kiss_db_item_t* item); -typedef kiss_bool (*kiss_select_callback)(kiss_db_item_t* item); +typedef kiss_bool (*kiss_insert_callback)(kiss_db_item_t* item, void* user_ptr); +typedef kiss_bool (*kiss_delete_callback)(kiss_db_item_t* item, void* user_ptr); +typedef kiss_bool (*kiss_select_callback)(kiss_db_item_t* item, void* user_ptr); kiss_db_ptr kiss_db_create(const kiss_db_config_t* config, kiss_db_error_t* error); @@ -65,14 +65,14 @@ kiss_bool kiss_db_commit(kiss_db_ptr ptr, kiss_db_error_t* error); kiss_bool kiss_db_rollback(kiss_db_ptr ptr, kiss_db_error_t* error); kiss_bool kiss_db_insert(kiss_db_ptr ptr, kiss_db_item_t* item, kiss_bool update, kiss_db_error_t* error); -kiss_bool kiss_db_insert_bulk(kiss_db_ptr ptr, kiss_bool update, kiss_insert_callback callback, kiss_db_error_t* error); +kiss_bool kiss_db_insert_bulk(kiss_db_ptr ptr, kiss_bool update, kiss_insert_callback callback, void* callback_user_ptr, kiss_db_error_t* error); -kiss_bool kiss_db_delete(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_size, kiss_delete_callback callback, kiss_db_error_t* error); +kiss_bool kiss_db_delete(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_size, kiss_delete_callback callback, void* callback_user_ptr, kiss_db_error_t* error); -kiss_bool kiss_db_select(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_size, kiss_select_callback callback, kiss_db_error_t* error); -kiss_bool kiss_db_select_bulk(kiss_db_ptr ptr, kiss_select_callback callback, kiss_db_error_t* error); +kiss_bool kiss_db_select(kiss_db_ptr ptr, const void* in_key, kiss_uint in_key_size, kiss_select_callback callback, void* callback_user_ptr, kiss_db_error_t* error); +kiss_bool kiss_db_select_bulk(kiss_db_ptr ptr, kiss_select_callback callback, void* callback_user_ptr, kiss_db_error_t* error); -kiss_int64 kiss_db_count(kiss_db_ptr ptr, kiss_db_error_t* error); +kiss_int64 kiss_db_count(kiss_db_ptr ptr); #ifdef __cplusplus