From e26d7b89815403a17a076e6a1e17c50c4793c3b4 Mon Sep 17 00:00:00 2001 From: Mahdi Sharifi Date: Tue, 9 Jan 2024 01:51:03 +0330 Subject: [PATCH] Implement the buffer feature --- README.md | 45 ++- bench/bench-with-builtin.js | 135 +-------- src/drivers/js/index.js | 53 +--- src/drivers/js/nodejs/functions/common.zig | 55 ++++ src/drivers/js/nodejs/functions/del.zig | 22 ++ src/drivers/js/nodejs/functions/get.zig | 24 ++ src/drivers/js/nodejs/functions/set.zig | 36 +++ src/drivers/js/nodejs/index.js | 17 -- src/drivers/js/nodejs/main.zig | 327 ++------------------- src/drivers/js/nodejs/napi-bindings.zig | 2 +- src/drivers/js/nodejs/symbols.zig | 11 +- src/drivers/js/tests/kivi.js | 86 +++--- 12 files changed, 262 insertions(+), 551 deletions(-) create mode 100644 src/drivers/js/nodejs/functions/common.zig create mode 100644 src/drivers/js/nodejs/functions/del.zig create mode 100644 src/drivers/js/nodejs/functions/get.zig create mode 100644 src/drivers/js/nodejs/functions/set.zig diff --git a/README.md b/README.md index eb76ed1..7614ce8 100644 --- a/README.md +++ b/README.md @@ -7,34 +7,31 @@ Kivi is a high-performance in-memory key-value database written in the Zig progr ## Latest benchmark: ``` JsMap: -┌─────────┬─────────────────┬─────────────────────┬────────────────────┬────────────────────────┬───────────────────┬───────────────────────┐ -│ (index) │ totalLookupTime │ totalBulkLookupTime │ totalInsertionTime │ totalBulkInsertionTime │ totalDeletionTime │ totalBulkDeletionTime │ -├─────────┼─────────────────┼─────────────────────┼────────────────────┼────────────────────────┼───────────────────┼───────────────────────┤ -│ 0 │ '134.17 ms' │ '138.9 ms' │ '155.65 ms' │ '301.3 ms' │ '197.46 ms' │ '215.23 ms' │ -│ 1 │ '137.91 ms' │ '135.72 ms' │ '151.54 ms' │ '167.48 ms' │ '242.4 ms' │ '217.72 ms' │ -│ average │ '136.04 ms' │ '137.31 ms' │ '153.59 ms' │ '234.39 ms' │ '219.93 ms' │ '216.48 ms' │ -└─────────┴─────────────────┴─────────────────────┴────────────────────┴────────────────────────┴───────────────────┴───────────────────────┘ +┌─────────┬─────────────────┬────────────────────┬───────────────────┐ +│ (index) │ totalLookupTime │ totalInsertionTime │ totalDeletionTime │ +├─────────┼─────────────────┼────────────────────┼───────────────────┤ +│ 0 │ '1112.18 ms' │ '1258.46 ms' │ '1272.44 ms' │ +│ 1 │ '1103.26 ms' │ '1199.46 ms' │ '1281.72 ms' │ +│ average │ '1107.72 ms' │ '1228.96 ms' │ '1277.08 ms' │ +└─────────┴─────────────────┴────────────────────┴───────────────────┘ Kivi: -┌─────────┬─────────────────┬─────────────────────┬────────────────────┬────────────────────────┬───────────────────┬───────────────────────┐ -│ (index) │ totalLookupTime │ totalBulkLookupTime │ totalInsertionTime │ totalBulkInsertionTime │ totalDeletionTime │ totalBulkDeletionTime │ -├─────────┼─────────────────┼─────────────────────┼────────────────────┼────────────────────────┼───────────────────┼───────────────────────┤ -│ 0 │ '352.02 ms' │ '774.82 ms' │ '341.96 ms' │ '555.72 ms' │ '346.08 ms' │ '793.16 ms' │ -│ 1 │ '353.9 ms' │ '791.95 ms' │ '341.23 ms' │ '543.91 ms' │ '367.19 ms' │ '805.19 ms' │ -│ average │ '352.96 ms' │ '783.38 ms' │ '341.59 ms' │ '549.81 ms' │ '356.64 ms' │ '799.18 ms' │ -└─────────┴─────────────────┴─────────────────────┴────────────────────┴────────────────────────┴───────────────────┴───────────────────────┘ +┌─────────┬─────────────────┬────────────────────┬───────────────────┐ +│ (index) │ totalLookupTime │ totalInsertionTime │ totalDeletionTime │ +├─────────┼─────────────────┼────────────────────┼───────────────────┤ +│ 0 │ '1350.07 ms' │ '559.04 ms' │ '1355.66 ms' │ +│ 1 │ '1349.39 ms' │ '557.93 ms' │ '1346.2 ms' │ +│ average │ '1349.73 ms' │ '558.49 ms' │ '1350.93 ms' │ +└─────────┴─────────────────┴────────────────────┴───────────────────┘ This table shows how much JsMap is faster than Kivi: -┌───────────────┬─────────┐ -│ (index) │ Values │ -├───────────────┼─────────┤ -│ lookup │ '2.59x' │ -│ insertion │ '2.22x' │ -│ deletion │ '1.62x' │ -│ bulkLookup │ '5.71x' │ -│ bulkInsertion │ '2.35x' │ -│ bulkDeletion │ '3.69x' │ -└───────────────┴─────────┘ +┌───────────┬─────────┐ +│ (index) │ Values │ +├───────────┼─────────┤ +│ lookup │ '1.22x' │ +│ insertion │ '0.45x' │ +│ deletion │ '1.06x' │ +└───────────┴─────────┘ ``` ## Code of conduct: diff --git a/bench/bench-with-builtin.js b/bench/bench-with-builtin.js index f1ef8e7..1d68872 100644 --- a/bench/bench-with-builtin.js +++ b/bench/bench-with-builtin.js @@ -34,61 +34,46 @@ const roundToTwoDecimal = (num) => +(Math.round(num + "e+2") + "e-2"); const benchmarkDeletion = (data, o) => { const startingTime = performance.now(); for (const item of data) { - assert(`${o.name} deletion`, o.del(item.key), item.value); + assert( + `${o.name} deletion`, + o.del(Buffer.from(item.key, "utf8")).toString(), + Buffer.from(item.value, "utf8").toString() + ); } return performance.now() - startingTime; }; -const benchmarkBulkDeletion = (data, o) => { - const startingTime = performance.now(); - o.bulkDel(dataKeys); - return performance.now() - startingTime; -}; const benchmarkLookup = (data, o) => { const startingTime = performance.now(); for (const item of data) { - assert(`${o.name} lookup`, o.get(item.key), item.value); + assert( + `${o.name} lookup`, + o.get(Buffer.from(item.key, "utf8")).toString(), + Buffer.from(item.value, "utf8").toString() + ); } return performance.now() - startingTime; }; -const benchmarkBulkLookup = (data, o) => { - const startingTime = performance.now(); - o.bulkGet(dataKeys); - return performance.now() - startingTime; -}; const benchmarkInsertion = (data, o) => { const startingTime = performance.now(); for (const item of data) { - o.set(item.key, item.value); + o.set(Buffer.from(item.key, "utf8"), Buffer.from(item.value, "utf8")); } return performance.now() - startingTime; }; -const benchmarkBulkInsertion = (data, o) => { - const startingTime = performance.now(); - o.bulkSet(data); - return performance.now() - startingTime; -}; let averageLogResult = []; const wrapInHumanReadable = (value) => { return { totalLookupTime: roundToTwoDecimal(value.totalLookupTime) + " ms", - totalBulkLookupTime: roundToTwoDecimal(value.totalBulkLookupTime) + " ms", totalInsertionTime: roundToTwoDecimal(value.totalInsertionTime) + " ms", - totalBulkInsertionTime: - roundToTwoDecimal(value.totalBulkInsertionTime) + " ms", totalDeletionTime: roundToTwoDecimal(value.totalDeletionTime) + " ms", - totalBulkDeletionTime: - roundToTwoDecimal(value.totalBulkDeletionTime) + " ms", }; }; const formatLogResult = (value) => { return { totalLookupTime: value.lookupDuration, - totalBulkLookupTime: value.bulkLookupDuration, totalInsertionTime: value.insertionDuration, - totalBulkInsertionTime: value.bulkInsertionDuration, totalDeletionTime: value.deletionDuration, - totalBulkDeletionTime: value.bulkDeletionDuration, }; }; const logResults = (name, durationArr, averageArg) => { @@ -101,11 +86,8 @@ const logResults = (name, durationArr, averageArg) => { averageLogResult.push({ name, totalLookupTime: average.totalLookupTime, - totalBulkLookupTime: average.totalBulkLookupTime, totalInsertionTime: average.totalInsertionTime, - totalBulkInsertionTime: average.totalBulkInsertionTime, totalDeletionTime: average.totalDeletionTime, - totalBulkDeletionTime: average.totalBulkDeletionTime, }); console.log(`\n${name}:`); @@ -135,21 +117,6 @@ const logRatio = () => { averageLogResult[1].totalDeletionTime / averageLogResult[0].totalDeletionTime ) + "x", - bulkLookup: - roundToTwoDecimal( - averageLogResult[1].totalBulkLookupTime / - averageLogResult[0].totalBulkLookupTime - ) + "x", - bulkInsertion: - roundToTwoDecimal( - averageLogResult[1].totalBulkInsertionTime / - averageLogResult[0].totalBulkInsertionTime - ) + "x", - bulkDeletion: - roundToTwoDecimal( - averageLogResult[1].totalBulkDeletionTime / - averageLogResult[0].totalBulkDeletionTime - ) + "x", }); }; @@ -182,49 +149,20 @@ const builtinMapBenchmark = () => { name: "JsMap", map: new Map(), get: function (k) { - return this.map.get(k); - }, - bulkGet: function (ks) { - const res = []; - for (const k of ks) { - res.push(this.map.get(k)); - } - return res; + return this.map.get(k.toString()); }, set: function (k, v) { - return this.map.set(k, v); - }, - bulkSet: function (data) { - const res = []; - for (const kv of data) { - res.push(this.map.set(kv.key, kv.value)); - } - return res; + return this.map.set(k.toString(), v); }, del: function (k) { - const v = this.map.get(k); - this.map.delete(k); + const v = this.map.get(k.toString()); + this.map.delete(k.toString()); return v; }, - bulkDel: function (ks) { - const res = []; - for (const k of ks) { - res.push(this.map.get(k)); - this.map.delete(k); - } - return res; - }, destroy: function () { return this.map.clear(); }, }; - const bulkInsertionDuration = benchmarkBulkInsertion(data, o); - const bulkLookupDuration = benchmarkBulkLookup(data, o); - const bulkDeletionDuration = benchmarkBulkDeletion(data, o); - o.destroy(); - - o.map = new Map(); - const insertionDuration = benchmarkInsertion(data, o); const lookupDuration = benchmarkLookup(data, o); const deletionDuration = benchmarkDeletion(data, o); @@ -234,31 +172,18 @@ const builtinMapBenchmark = () => { insertionDuration, lookupDuration, deletionDuration, - bulkInsertionDuration, - bulkLookupDuration, - bulkDeletionDuration, }); if (average.insertionDuration === 0) { average = { insertionDuration, lookupDuration, deletionDuration, - bulkInsertionDuration, - bulkInsertionDuration, - bulkLookupDuration, - bulkDeletionDuration, }; } else { average = { insertionDuration: (average.insertionDuration + insertionDuration) / 2, - bulkInsertionDuration: - (average.bulkInsertionDuration + bulkInsertionDuration) / 2, lookupDuration: (average.lookupDuration + lookupDuration) / 2, - bulkLookupDuration: - (average.bulkLookupDuration + bulkLookupDuration) / 2, deletionDuration: (average.deletionDuration + deletionDuration) / 2, - bulkDeletionDuration: - (average.bulkDeletionDuration + bulkDeletionDuration) / 2, }; } } @@ -278,32 +203,16 @@ const kiviBenchmark = () => { get: function (k) { return this.map.get(k); }, - bulkGet: function (ks) { - return this.map.bulkGet(ks); - }, set: function (k, v) { return this.map.set(k, v); }, - bulkSet: function (data) { - return this.map.bulkSet(data); - }, del: function (k) { - return this.map.fetchDel(k); - }, - bulkDel: function (ks) { - return this.map.bulkFetchDel(ks); + return this.map.del(k); }, destroy: function () { return this.map.destroy(); }, }; - const bulkInsertionDuration = benchmarkBulkInsertion(data, o); - const bulkLookupDuration = benchmarkBulkLookup(data, o); - const bulkDeletionDuration = benchmarkBulkDeletion(data, o); - o.destroy(); - - o.map = new Kivi(); - const insertionDuration = benchmarkInsertion(data, o); const lookupDuration = benchmarkLookup(data, o); const deletionDuration = benchmarkDeletion(data, o); @@ -313,30 +222,18 @@ const kiviBenchmark = () => { insertionDuration, lookupDuration, deletionDuration, - bulkInsertionDuration, - bulkLookupDuration, - bulkDeletionDuration, }); if (average.insertionDuration === 0) { average = { insertionDuration, lookupDuration, deletionDuration, - bulkInsertionDuration, - bulkLookupDuration, - bulkDeletionDuration, }; } else { average = { insertionDuration: (average.insertionDuration + insertionDuration) / 2, - bulkInsertionDuration: - (average.bulkInsertionDuration + bulkInsertionDuration) / 2, lookupDuration: (average.lookupDuration + lookupDuration) / 2, - bulkLookupDuration: - (average.bulkLookupDuration + bulkLookupDuration) / 2, deletionDuration: (average.deletionDuration + deletionDuration) / 2, - bulkDeletionDuration: - (average.bulkDeletionDuration + bulkDeletionDuration) / 2, }; } } diff --git a/src/drivers/js/index.js b/src/drivers/js/index.js index bfc3e1a..ef7c0dd 100644 --- a/src/drivers/js/index.js +++ b/src/drivers/js/index.js @@ -51,10 +51,7 @@ export class Kivi { this.#InnerKivi = new NodeKivi(); } - if (!this.#InnerKivi.init()) { - console.log(this.#InnerKivi.init()); - throw new Error(`Failed to initialize a Kivi!`); - } + return this.#InnerKivi.init(); } /** * Releases the allocated memory and deinitializes Kivi. @@ -72,15 +69,6 @@ export class Kivi { get(key) { return this.#InnerKivi.get(key); } - /** - * Returns values of given keys. - * This function is noticeably faster when multiple data is given due to process in parallel. - * @param {string[]} keys - * @returns {(string|null)[]} - */ - bulkGet(keys) { - return this.#InnerKivi.bulkGet(keys); - } /** * Sets a key to the given value. @@ -89,18 +77,7 @@ export class Kivi { * @returns {boolean} */ set(key, value) { - if (!this.#InnerKivi.set(key, value)) { - throw new Error("Failed to insert!"); - } - } - /** - * Sets values to given keys. - * This function is noticeably faster when multiple data is given due to process in parallel. - * @param {{key: string, value: string}[]} data - * @returns {boolean[]} - */ - bulkSet(data) { - return this.#InnerKivi.bulkSet(data); + return this.#InnerKivi.set(key, value); } /** @@ -111,30 +88,4 @@ export class Kivi { del(key) { return this.#InnerKivi.del(key); } - /** - * Removes a key with its value and returns the value. - * @param {string} key - * @returns {string} - */ - fetchDel(key) { - return this.#InnerKivi.fetchDel(key); - } - /** - * Removes keys with their values and returns the values. - * This function is noticeably faster when multiple data is given due to process in parallel. - * @param {string[]} keys - * @returns {string[]} - */ - bulkFetchDel(keys) { - return this.#InnerKivi.bulkFetchDel(keys); - } - /** - * Removes keys with their values. - * This function is noticeably faster when multiple data is given due to process in parallel. - * @param {string[]} keys - * @returns {void} - */ - bulkDel(keys) { - return this.#InnerKivi.bulkDel(keys); - } } diff --git a/src/drivers/js/nodejs/functions/common.zig b/src/drivers/js/nodejs/functions/common.zig new file mode 100644 index 0000000..ed2124d --- /dev/null +++ b/src/drivers/js/nodejs/functions/common.zig @@ -0,0 +1,55 @@ +const Kivi = @import("Kivi"); +const ntypes = @import("../napi-bindings.zig"); +const symbols = @import("../symbols.zig"); + +pub fn get_null(env: ntypes.napi_env) ntypes.napi_value { + var null_value: ntypes.napi_value = undefined; + _ = symbols.napi_get_null(env, &null_value); + return null_value; +} +pub fn get_undefined(env: ntypes.napi_env) ntypes.napi_value { + var undefined_value: ntypes.napi_value = undefined; + _ = symbols.napi_get_undefined(env, &undefined_value); + return undefined_value; +} + +pub fn exception_ret(env: ntypes.napi_env, msg: [:0]const u8) ntypes.napi_value { + _ = symbols.napi_throw_error(env, null, msg); + return get_undefined(env); +} + +pub fn get_buffer_string(env: ntypes.napi_env, buf: ntypes.napi_value) ![]u8 { + var len: usize = 0; + var data: ?*anyopaque = undefined; + if (symbols.napi_get_buffer_info(env, buf, &data, &len) == ntypes.napi_ok and len != 0) { + return @as([*]u8, @ptrCast(@alignCast(data)))[0..len]; + } + return error.InvalidBuffer; +} +pub fn create_buffer_string(env: ntypes.napi_env, buf: []u8) !ntypes.napi_value { + var newbuf: ntypes.napi_value = undefined; + const lastbuf = @as(?*anyopaque, @ptrCast(@alignCast(buf.ptr))); + if (symbols.napi_create_buffer_copy(env, buf.len, lastbuf, null, &newbuf) == ntypes.napi_ok) { + return newbuf; + } + return error.FailedToCreateBufferFromString; +} + +pub inline fn parse_args(env: ntypes.napi_env, info: ntypes.napi_callback_info, comptime args_count: comptime_int) ![args_count]ntypes.napi_value { + var argc_napi: usize = args_count; + var args: [args_count]ntypes.napi_value = undefined; + if (symbols.napi_get_cb_info(env, info, &argc_napi, &args, null, null) == ntypes.napi_ok) { + return args; + } + + return error.NoArgs; +} +pub fn get_kivi(env: ntypes.napi_env, arraybuffer: ntypes.napi_value) !*Kivi { + var length: usize = undefined; + var ptr: ?*anyopaque = null; + if (symbols.napi_get_arraybuffer_info(env, arraybuffer, &ptr, &length) == ntypes.napi_ok) { + return @as(*Kivi, @ptrCast(@alignCast(ptr))); + } + + return error.FailedToGetKiviInstance; +} diff --git a/src/drivers/js/nodejs/functions/del.zig b/src/drivers/js/nodejs/functions/del.zig new file mode 100644 index 0000000..17daf9b --- /dev/null +++ b/src/drivers/js/nodejs/functions/del.zig @@ -0,0 +1,22 @@ +const Kivi = @import("Kivi"); +const common = @import("common.zig"); +const ntypes = @import("../napi-bindings.zig"); +const symbols = @import("../symbols.zig"); + +pub export fn kivi_del_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { + const args = common.parse_args(env, info, 2) catch { + return common.exception_ret(env, "Invalid Arguments!"); + }; + + const self = common.get_kivi(env, args[0]) catch { + return common.exception_ret(env, "Invalid Kivi instance!"); + }; + const key = common.get_buffer_string(env, args[1]) catch { + return common.exception_ret(env, "Empty/invalid key buffer!"); + }; + const value = self.del_slice(key) catch return common.get_null(env); + + return common.create_buffer_string(env, value) catch { + return common.exception_ret(env, "Failed to create a buffer for the results!"); + }; +} diff --git a/src/drivers/js/nodejs/functions/get.zig b/src/drivers/js/nodejs/functions/get.zig new file mode 100644 index 0000000..30efbaa --- /dev/null +++ b/src/drivers/js/nodejs/functions/get.zig @@ -0,0 +1,24 @@ +const Kivi = @import("Kivi"); +const common = @import("common.zig"); +const ntypes = @import("../napi-bindings.zig"); +const symbols = @import("../symbols.zig"); + +const std = @import("std"); + +pub export fn kivi_get_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { + const args = common.parse_args(env, info, 2) catch { + return common.exception_ret(env, "Invalid Arguments!"); + }; + + const self = common.get_kivi(env, args[0]) catch { + return common.exception_ret(env, "Invalid Kivi instance!"); + }; + const key = common.get_buffer_string(env, args[1]) catch { + return common.exception_ret(env, "Empty/invalid key buffer!"); + }; + const value = self.get_slice(key) catch return common.get_null(env); + + return common.create_buffer_string(env, value) catch { + return common.exception_ret(env, "Failed to create a buffer for the results!"); + }; +} diff --git a/src/drivers/js/nodejs/functions/set.zig b/src/drivers/js/nodejs/functions/set.zig new file mode 100644 index 0000000..21b79cd --- /dev/null +++ b/src/drivers/js/nodejs/functions/set.zig @@ -0,0 +1,36 @@ +const Kivi = @import("Kivi"); +const common = @import("common.zig"); +const ntypes = @import("../napi-bindings.zig"); +const symbols = @import("../symbols.zig"); + +pub export fn kivi_set_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { + const args = common.parse_args(env, info, 3) catch { + return common.exception_ret(env, "Invalid Arguments!"); + }; + + const self = common.get_kivi(env, args[0]) catch { + return common.exception_ret(env, "Invalid Kivi instance!"); + }; + const key = common.get_buffer_string(env, args[1]) catch { + return common.exception_ret(env, "Empty/invalid key buffer!"); + }; + const value = common.get_buffer_string(env, args[2]) catch { + return common.exception_ret(env, "Empty/invalid value buffer!"); + }; + const reserved_key = self.reserve_key(key.len) catch { + return common.exception_ret(env, "Not enough memory to store the key!"); + }; + const reserved_value = self.reserve_value(value.len) catch { + self.mem_allocator.free(reserved_key); + return common.exception_ret(env, "Not enough memory to store the value!"); + }; + @memcpy(reserved_key, key); + @memcpy(reserved_value, value); + self.putEntry(reserved_key, reserved_value) catch { + self.mem_allocator.free(reserved_key); + self.mem_allocator.free(reserved_value); + return common.exception_ret(env, "Not enough memory to fit the new key/value pair!"); + }; + + return common.get_undefined(env); +} diff --git a/src/drivers/js/nodejs/index.js b/src/drivers/js/nodejs/index.js index 496ab07..6977c27 100644 --- a/src/drivers/js/nodejs/index.js +++ b/src/drivers/js/nodejs/index.js @@ -44,27 +44,10 @@ export class NodeKivi { get(key) { return addon.kivi_get(this.#buf, key); } - bulkGet(keys) { - return addon.kivi_bulk_get(this.#buf, keys); - } - set(key, value) { return addon.kivi_set(this.#buf, key, value); } - bulkSet(data) { - return addon.kivi_bulk_set(this.#buf, data); - } - del(key) { return addon.kivi_del(this.#buf, key); } - fetchDel(key) { - return addon.kivi_fetch_del(this.#buf, key); - } - bulkFetchDel(keys) { - return addon.kivi_bulk_fetch_del(this.#buf, keys); - } - bulkDel(keys) { - return addon.kivi_bulk_del(this.#buf, keys); - } } diff --git a/src/drivers/js/nodejs/main.zig b/src/drivers/js/nodejs/main.zig index d13486a..c25f71a 100644 --- a/src/drivers/js/nodejs/main.zig +++ b/src/drivers/js/nodejs/main.zig @@ -1,301 +1,38 @@ const std = @import("std"); const Kivi = @import("Kivi"); -const symbols = @import("symbols.zig"); const ntypes = @import("napi-bindings.zig"); +const symbols = @import("symbols.zig"); -var allocator = std.heap.page_allocator; -const KEYS_DEFAULT_BUF_SIZE: comptime_int = 500 * 1024; - -inline fn get_args(env: ntypes.napi_env, info: ntypes.napi_callback_info, comptime args_count: comptime_int) ![args_count]ntypes.napi_value { - var args: [args_count]ntypes.napi_value = undefined; - var argc_napi: usize = args_count; - const cb_status: ntypes.napi_status = symbols.napi_get_cb_info(env, info, &argc_napi, &args, null, null); - if (cb_status == ntypes.napi_ok) { - return args; - } - _ = symbols.napi_throw_error(env, "1", "INVALID_ARGS"); - - return error.NoArgs; -} -fn arg_to_kivi(env: ntypes.napi_env, arraybuffer: ntypes.napi_value) ?*Kivi { - var length: usize = undefined; - var ptr: ?*anyopaque = undefined; - if (symbols.napi_get_arraybuffer_info(env, arraybuffer, &ptr, &length) == ntypes.napi_ok) { - return @as(*Kivi, @ptrCast(@alignCast(ptr))); - } - return null; -} -fn get_string_length(env: ntypes.napi_env, arraybuffer: ntypes.napi_value) usize { - var len: usize = undefined; - if (symbols.napi_get_value_string_utf8(env, arraybuffer, null, 0, &len) == ntypes.napi_ok) { - return len; - } - return 0; -} -fn stack_string_to_buffer(env: ntypes.napi_env, arraybuffer: ntypes.napi_value, buf: []u8) usize { - var len: usize = undefined; - if (symbols.napi_get_value_string_utf8(env, arraybuffer, buf.ptr, buf.len, &len) == ntypes.napi_ok) { - return len; - } - return 0; -} -fn string_to_buffer(env: ntypes.napi_env, arraybuffer: ntypes.napi_value, buf: []u8) void { - var len: usize = undefined; - _ = symbols.napi_get_value_string_utf8(env, arraybuffer, buf.ptr, buf.len + 1, &len); -} -fn buffer_to_string(env: ntypes.napi_env, buf: []u8) ntypes.napi_value { - var string: ntypes.napi_value = undefined; - if (symbols.napi_create_string_utf8(env, buf.ptr, buf.len, &string) == ntypes.napi_ok) { - return string; - } - return null; -} -fn new_unint(env: ntypes.napi_env, value: u32) ntypes.napi_value { - var result: ntypes.napi_value = undefined; - if (symbols.napi_create_uint32(env, value, &result) == ntypes.napi_ok) { - return result; - } - return null; -} -fn new_null(env: ntypes.napi_env) ntypes.napi_value { - var null_value: ntypes.napi_value = undefined; - if (symbols.napi_get_null(env, &null_value) == ntypes.napi_ok) { - return null_value; - } - return null; -} -fn new_undefined(env: ntypes.napi_env) ntypes.napi_value { - var undefined_value: ntypes.napi_value = undefined; - if (symbols.napi_get_undefined(env, &undefined_value) == ntypes.napi_ok) { - return undefined_value; - } - return null; -} -inline fn allocate_temp_key(env: ntypes.napi_env, napi_buffer: ntypes.napi_value, should_be_freed: *bool) ![]u8 { - var temp_buf: [KEYS_DEFAULT_BUF_SIZE]u8 = undefined; - const length = get_string_length(env, napi_buffer); - - if (length > KEYS_DEFAULT_BUF_SIZE) { - const key_buf = allocator.alloc(u8, length) catch return error.Failed; - should_be_freed.* = true; - - string_to_buffer(env, napi_buffer, key_buf); - - return key_buf; - } else if (length == 0) { - return error.Failed; - } - - string_to_buffer(env, napi_buffer, &temp_buf); - - return temp_buf[0..length]; -} +const common = @import("functions/common.zig"); +pub const set = @import("functions/set.zig"); +pub const get = @import("functions/get.zig"); +pub const del = @import("functions/del.zig"); pub export fn kivi_init_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - const args = get_args(env, info, 1) catch { - return new_undefined(env); - }; - - const kivi_result: usize = arg_to_kivi(env, args[0]).?.init(&Kivi.Config{}) catch 0; - if (kivi_result == @sizeOf(Kivi)) { - return new_unint(env, @intCast(kivi_result)); - } - - return new_null(env); -} -pub export fn kivi_deinit_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - const args = get_args(env, info, 1) catch { - return new_undefined(env); - }; - - arg_to_kivi(env, args[0]).?.deinit(); - - return new_undefined(env); -} - -fn fetch_single(self: *Kivi, env: ntypes.napi_env, n_keybuf: ntypes.napi_value) ![]u8 { - var should_be_freed = false; - const key = try allocate_temp_key(env, n_keybuf, &should_be_freed); - defer { - if (should_be_freed) { - allocator.free(key); - } - } - - return self.get_slice(key); -} -pub export fn kivi_get_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - const args = get_args(env, info, 2) catch { - return new_undefined(env); + const args = common.parse_args(env, info, 1) catch { + return common.exception_ret(env, "Invalid Arguments!"); }; - const self = arg_to_kivi(env, args[0]).?; - const value = fetch_single(self, env, args[1]) catch { - return new_null(env); - }; - - return buffer_to_string(env, value); -} -pub export fn kivi_bulk_get_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - const args = get_args(env, info, 2) catch { - return new_undefined(env); - }; - - const self = arg_to_kivi(env, args[0]).?; - - var array_len: u32 = 0; - _ = symbols.napi_get_array_length(env, args[1], &array_len); - var result_arr: ntypes.napi_value = undefined; - _ = symbols.napi_create_array_with_length(env, @intCast(array_len), &result_arr); - - for (0..array_len) |idx| { - var napi_key: ntypes.napi_value = undefined; - _ = symbols.napi_get_element(env, args[1], @intCast(idx), &napi_key); - - var res: ntypes.napi_value = undefined; - if (fetch_single(self, env, napi_key)) |value| { - res = buffer_to_string(env, value); - } else |_| { - res = new_null(env); - } - - _ = symbols.napi_set_element(env, result_arr, @intCast(idx), res); - } - - return result_arr; -} - -fn set_single(self: *Kivi, env: ntypes.napi_env, n_keybuf: ntypes.napi_value, n_valbuf: ntypes.napi_value) !usize { - const key_len: usize = get_string_length(env, n_keybuf); - if (key_len == 0) { - return error.InvalidLength; - } - - const key_buf = try self.reserve_key(key_len); - string_to_buffer(env, n_keybuf, key_buf); - - const value_len: usize = get_string_length(env, n_valbuf); - if (value_len == 0) { - return error.InvalidLength; - } - - const value_buf = try self.reserve_value(value_len); - string_to_buffer(env, n_valbuf, value_buf); - - try self.putEntry(key_buf, value_buf); - - return value_len; -} -pub export fn kivi_set_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - const args = get_args(env, info, 3) catch { - return new_undefined(env); + const self = common.get_kivi(env, args[0]) catch { + return common.exception_ret(env, "Invalid Kivi instance!"); }; - - const self = arg_to_kivi(env, args[0]).?; - const value_len = set_single(self, env, args[1], args[2]) catch 0; - - return new_unint(env, @intCast(value_len)); -} -pub export fn kivi_bulk_set_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - const args = get_args(env, info, 2) catch { - return new_undefined(env); + _ = self.init(&Kivi.Config{}) catch { + return common.exception_ret(env, "Failed to initialize a Kivi instance!"); }; - const self = arg_to_kivi(env, args[0]).?; - - var array_len: u32 = 0; - _ = symbols.napi_get_array_length(env, args[1], &array_len); - var result_arr: ntypes.napi_value = undefined; - _ = symbols.napi_create_array_with_length(env, @intCast(array_len), &result_arr); - - for (0..array_len) |idx| { - var napi_kivi: ntypes.napi_value = undefined; - _ = symbols.napi_get_element(env, args[1], @intCast(idx), &napi_kivi); - var napi_key: ntypes.napi_value = undefined; - _ = symbols.napi_get_named_property(env, napi_kivi, "key", &napi_key); - var napi_value: ntypes.napi_value = undefined; - _ = symbols.napi_get_named_property(env, napi_kivi, "value", &napi_value); - - var res = true; - if (set_single(self, env, napi_key, napi_value)) |_| {} else |_| { - res = false; - } - - var napi_res: ntypes.napi_value = undefined; - _ = symbols.napi_get_boolean(env, res, &napi_res); - _ = symbols.napi_set_element(env, result_arr, @intCast(idx), napi_res); - } - - return result_arr; + return common.get_undefined(env); } - -fn del_single(self: *Kivi, env: ntypes.napi_env, n_keybuf: ntypes.napi_value) ![]u8 { - var should_be_freed = false; - const key = try allocate_temp_key(env, n_keybuf, &should_be_freed); - defer { - if (should_be_freed) { - allocator.free(key); - } - } - - return try self.del_slice(key); -} -pub export fn kivi_fetch_del_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - const args = get_args(env, info, 2) catch { - return new_undefined(env); - }; - - const self = arg_to_kivi(env, args[0]).?; - const value = del_single(self, env, args[1]) catch { - return new_null(env); +pub export fn kivi_deinit_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { + const args = common.parse_args(env, info, 1) catch { + return common.exception_ret(env, "Invalid Arguments!"); }; - const string = buffer_to_string(env, value); - - self.del_value(value); - - return string; -} -pub export fn kivi_del_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - _ = kivi_fetch_del_js(env, info); - return new_undefined(env); -} -pub export fn kivi_bulk_fetch_del_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - const args = get_args(env, info, 2) catch { - return new_undefined(env); + const self = common.get_kivi(env, args[0]) catch { + return common.exception_ret(env, "Invalid Kivi instance!"); }; + self.deinit(); - const self = arg_to_kivi(env, args[0]).?; - - var array_len: u32 = 0; - _ = symbols.napi_get_array_length(env, args[1], &array_len); - var result_arr: ntypes.napi_value = undefined; - _ = symbols.napi_create_array_with_length(env, @intCast(array_len), &result_arr); - - for (0..array_len) |idx| { - var napi_key: ntypes.napi_value = undefined; - _ = symbols.napi_get_element(env, args[1], @intCast(idx), &napi_key); - - var res: ntypes.napi_value = undefined; - if (del_single(self, env, napi_key)) |value| { - res = buffer_to_string(env, value); - self.del_value(value); - } else |_| { - res = new_null(env); - } - - _ = symbols.napi_set_element(env, result_arr, @intCast(idx), res); - } - - return result_arr; -} -pub export fn kivi_bulk_del_js(env: ntypes.napi_env, info: ntypes.napi_callback_info) ntypes.napi_value { - _ = kivi_bulk_fetch_del_js(env, info); - return new_undefined(env); -} - -pub export fn node_api_module_get_api_version_v1() i32 { - return 6; + return common.get_undefined(env); } const Function = struct { @@ -305,34 +42,20 @@ const Function = struct { const functions = [_]Function{ Function{ .name = "kivi_init", .method = &kivi_init_js, -}, Function{ - .name = "kivi_bulk_get", - .method = &kivi_bulk_get_js, }, Function{ .name = "kivi_get", - .method = &kivi_get_js, + .method = &get.kivi_get_js, }, Function{ .name = "kivi_set", - .method = &kivi_set_js, -}, Function{ - .name = "kivi_bulk_set", - .method = &kivi_bulk_set_js, + .method = &set.kivi_set_js, }, Function{ .name = "kivi_del", - .method = &kivi_del_js, -}, Function{ - .name = "kivi_fetch_del", - .method = &kivi_fetch_del_js, -}, Function{ - .name = "kivi_bulk_fetch_del", - .method = &kivi_bulk_fetch_del_js, -}, Function{ - .name = "kivi_bulk_del", - .method = &kivi_bulk_del_js, + .method = &del.kivi_del_js, }, Function{ .name = "kivi_deinit", .method = &kivi_deinit_js, } }; + pub export fn napi_register_module_v1(env: ntypes.napi_env, exports: ntypes.napi_value) ntypes.napi_value { symbols.init_symbols(); @@ -357,3 +80,7 @@ pub export fn napi_register_module_v1(env: ntypes.napi_env, exports: ntypes.napi return exports; } + +pub export fn node_api_module_get_api_version_v1() i32 { + return 6; +} diff --git a/src/drivers/js/nodejs/napi-bindings.zig b/src/drivers/js/nodejs/napi-bindings.zig index f790472..df415e1 100644 --- a/src/drivers/js/nodejs/napi-bindings.zig +++ b/src/drivers/js/nodejs/napi-bindings.zig @@ -366,7 +366,7 @@ pub const __PRAGMA_REDEFINE_EXTNAME = @as(c_int, 1); pub const __VERSION__ = "Clang 16.0.6 (https://github.com/ziglang/zig-bootstrap 34644ad5032c58e39327d33d7f96d63d7c330003)"; pub const __OBJC_BOOL_IS_BOOL = @as(c_int, 0); pub const __CONSTANT_CFSTRINGS__ = @as(c_int, 1); -pub const __clang_literal_encoding__ = "UTF-8"; +pub const __clang_literal_encoding__ = "utf8"; pub const __clang_wide_literal_encoding__ = "UTF-32"; pub const __ORDER_LITTLE_ENDIAN__ = @as(c_int, 1234); pub const __ORDER_BIG_ENDIAN__ = @as(c_int, 4321); diff --git a/src/drivers/js/nodejs/symbols.zig b/src/drivers/js/nodejs/symbols.zig index 32e3b43..3e60d53 100644 --- a/src/drivers/js/nodejs/symbols.zig +++ b/src/drivers/js/nodejs/symbols.zig @@ -18,6 +18,9 @@ pub var napi_create_array_with_length: *const @TypeOf(c.napi_create_array_with_l pub var napi_set_element: *const @TypeOf(c.napi_set_element) = undefined; pub var napi_get_named_property: *const @TypeOf(c.napi_get_named_property) = undefined; pub var napi_get_boolean: *const @TypeOf(c.napi_get_boolean) = undefined; +pub var napi_is_buffer: *const @TypeOf(c.napi_is_buffer) = undefined; +pub var napi_is_arraybuffer: *const @TypeOf(c.napi_is_arraybuffer) = undefined; +pub var napi_create_buffer_copy: *const @TypeOf(c.napi_create_buffer_copy) = undefined; const kernel32 = std.os.windows.kernel32; const is_windows = builtin.target.os.tag == .windows; @@ -49,6 +52,9 @@ pub fn init_symbols() void { napi_set_element = load_sym(*const @TypeOf(c.napi_set_element), "napi_set_element"); napi_get_named_property = load_sym(*const @TypeOf(c.napi_get_named_property), "napi_get_named_property"); napi_get_boolean = load_sym(*const @TypeOf(c.napi_get_boolean), "napi_get_boolean"); + napi_is_buffer = load_sym(*const @TypeOf(c.napi_is_buffer), "napi_is_buffer"); + napi_is_arraybuffer = load_sym(*const @TypeOf(c.napi_is_arraybuffer), "napi_is_arraybuffer"); + napi_create_buffer_copy = load_sym(*const @TypeOf(c.napi_create_buffer_copy), "napi_create_buffer_copy"); } else { napi_create_string_utf8 = c.napi_create_string_utf8; napi_create_uint32 = c.napi_create_uint32; @@ -64,7 +70,8 @@ pub fn init_symbols() void { napi_get_array_length = c.napi_get_array_length; napi_create_array_with_length = c.napi_create_array_with_length; napi_set_element = c.napi_set_element; - napi_get_named_property = c.napi_get_named_property; - napi_get_boolean = c.napi_get_boolean; + napi_is_buffer = c.napi_is_buffer; + napi_is_arraybuffer = c.napi_is_arraybuffer; + napi_create_buffer_copy = c.napi_create_buffer_copy; } } diff --git a/src/drivers/js/tests/kivi.js b/src/drivers/js/tests/kivi.js index 2f0cdf2..71ee339 100644 --- a/src/drivers/js/tests/kivi.js +++ b/src/drivers/js/tests/kivi.js @@ -2,50 +2,62 @@ import { Kivi } from "../index.js"; const run = (config) => { const assert = (name, left, right) => { - if (JSON.stringify(left) !== JSON.stringify(right)) { - console.error("Left:", left); - console.error("Right:", right); - throw new Error(`Assertion '${name}' failed!`); + if (Buffer.isBuffer(left) && Buffer.isBuffer(right)) { + if (left.toString("utf8") == right.toString("utf8")) { + return; + } + } else { + if (left == right) { + return; + } } + + throw new Error( + `Assertion '${name}' failed! Left was '${left}' and right was '${right}'.` + ); }; - const c = new Kivi(config); + const k = new Kivi(config); + + assert("Null-if-uninitialized", k.get(Buffer.from("foo", "utf8")), null); + + k.set(Buffer.from("foo", "utf8"), Buffer.from("bar", "utf8")); - assert("Null-if-uninitialized", c.get("foo"), null); - c.set("foo", "bar"); - assert("Assert-after-set", c.get("foo"), "bar"); - assert("Value-when-delete", c.fetchDel("foo"), "bar"); - assert("Value-after-delete", c.get("foo"), null); + assert( + "Assert-after-set", + Buffer.from(k.get(Buffer.from("foo", "utf8")), "utf8"), + Buffer.from("bar", "utf8") + ); assert( - "Value-set-with-bulk", - c.bulkSet([ - { key: "foo1", value: "bar1" }, - { key: "foo2", value: "bar2" }, - { key: "foo3", value: "bar3" }, - ]), - [true, true, true] + "Value-when-delete", + k.del(Buffer.from("foo", "utf8")), + Buffer.from("bar", "utf8") ); - assert("Value-get-with-bulk", c.bulkGet(["foo", "foo1", "foo2", "foo3"]), [ - null, - "bar1", - "bar2", - "bar3", - ]); - assert("Value-with-bulk", c.bulkFetchDel(["foo", "foo1", "foo2", "foo3"]), [ - null, - "bar1", - "bar2", - "bar3", - ]); - assert("Value-get-with-bulk", c.bulkGet(["foo", "foo1", "foo2", "foo3"]), [ - null, - null, - null, - null, - ]); - - c.destroy(); + + assert("Value-after-delete", k.get(Buffer.from("foo", "utf8")), null); + + // Do it again to assert the freelist + + assert("Null-if-uninitialized", k.get(Buffer.from("foo", "utf8")), null); + + k.set(Buffer.from("foo", "utf8"), Buffer.from("bar", "utf8")); + + assert( + "Assert-after-set", + k.get(Buffer.from("foo", "utf8")), + Buffer.from("bar", "utf8") + ); + + assert( + "Value-when-delete", + k.del(Buffer.from("foo", "utf8")), + Buffer.from("bar", "utf8") + ); + + assert("Value-after-delete", k.get(Buffer.from("foo", "utf8")), null); + + k.destroy(); }; run({ forceUseRuntimeFFI: false });