From e10a9e311fed2cb7690e1d05f1c2e5f934a6b19d Mon Sep 17 00:00:00 2001 From: Nathan Anderson Date: Thu, 16 Feb 2023 02:26:41 -0700 Subject: [PATCH] A failing spec'd out bitcask, a start of something fun :) --- .gitignore | 7 +++ README.md | 7 +++ build.zig | 34 ++++++++++ src/Bitcask/bitcask.zig | 136 ++++++++++++++++++++++++++++++++++++++++ src/main.zig | 24 +++++++ 5 files changed, 208 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.zig create mode 100644 src/Bitcask/bitcask.zig create mode 100644 src/main.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab961ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +zig-cache/ +zig-out/ +/release/ +/debug/ +/build/ +/build-*/ +/docgen_tmp/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..32609ba --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Bitcask - A Simple KV datastore + +Do it simply and do it well. + +## The Bitcask paper + +https://riak.com/assets/bitcask-intro.pdf \ No newline at end of file diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..128d28f --- /dev/null +++ b/build.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("bitcask", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const exe_tests = b.addTest("src/main.zig"); + exe_tests.setTarget(target); + exe_tests.setBuildMode(mode); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&exe_tests.step); +} diff --git a/src/Bitcask/bitcask.zig b/src/Bitcask/bitcask.zig new file mode 100644 index 0000000..491e87f --- /dev/null +++ b/src/Bitcask/bitcask.zig @@ -0,0 +1,136 @@ +const std = @import("std"); +const expect = std.testing.expect; + +pub const BitcaskFileError = error{ + AccessDenied, + OutOfMemory, + FileNotFound, +}; + +pub const BitCask = struct { + const FILE_THRESHOLD_SIZE = 1000; + + + // std.StringArrayHashMap + + // From the Bitcask paper, the API should look something like this + + //** TODO + + // What is the BitcaskHandel? How can I represent it? + // Implement opening and closing files + // Impement the spec + + // *** + + + // bitcask:open(DirectoryName, Opts) Open a new or existing Bitcask datastore with additional options. + // → BitCaskHandle | {error, any()} Valid options include read write (if this process is going to be a + // writer and not just a reader) and sync on put (if this writer would + // prefer to sync the write file after every write operation). + // The directory must be readable and writable by this process, and + // only one process may open a Bitcask with read write at a time. + // bitcask:open(DirectoryName) Open a new or existing Bitcask datastore for read-only access. + // → BitCaskHandle | {error, any()} The directory and all files in it must be readable by this process. + fn open(directory_name: []const u8) BitcaskFileError!void { + std.debug.print("Opening bitcask in dir {s}\n", .{directory_name}); + return error.FileNotFound; + } + + // bitcask:get(BitCaskHandle, Key) Retrieve a value by key from a Bitcask datastore. + // → not found | {ok, Value} + fn get(key: []const u8) error{NotImplemented}!void { + std.debug.print("Getting value with key {s}\n", .{key}); + return error.NotImplemented; + } + + // bitcask:put(BitCaskHandle, Key, Value) Store a key and value in a Bitcask datastore. + // → ok | {error, any()} + fn put() error{NotImplemented}!void { + return error.NotImplemented; + } + + // bitcask:delete(BitCaskHandle, Key) Delete a key from a Bitcask datastore. + // → ok | {error, any()} + fn delete() error{NotImplemented}!void { + return error.NotImplemented; + } + + // bitcask:list keys(BitCaskHandle) List all keys in a Bitcask datastore. + // → [Key] | {error, any()} + fn list() error{NotImplemented}!void { + return error.NotImplemented; + } + + // bitcask:fold(BitCaskHandle,Fun,Acc0) Fold over all K/V pairs in a Bitcask datastore. + // → Acc Fun is expected to be of the form: F(K,V,Acc0) → Acc. + fn fold() error{NotImplemented}!void { + return error.NotImplemented; + } + + // bitcask:merge(DirectoryName) Merge several data files within a Bitcask datastore into a more + // → ok | {error, any()} compact form. Also, produce hintfiles for faster startup. + fn merge() error{NotImplemented}!void { + return error.NotImplemented; + } + + // bitcask:sync(BitCaskHandle) Force any writes to sync to disk. + // → ok + fn sync() error{NotImplemented}!void { + return error.NotImplemented; + } + + // bitcask:close(BitCaskHandle) Close a Bitcask data store and flush all pending writes (if any) to disk + fn close() error{NotImplemented}!void { + return error.NotImplemented; + } +}; + +test "Bitcask spec implementation: open" { + const bc = BitCask; + try bc.open("File"); + // bc.open("File") catch |err| { + // try expect(err == error.FileNotFound); + // }; + +} + +test "Bitcask spec implementation: get" { + const bc = BitCask; + try bc.get("key"); +} + +test "Bitcask spec implementation: put" { + const bc = BitCask; + try bc.put(); +} + +test "Bitcask spec implementation: delete" { + const bc = BitCask; + try bc.delete(); +} + +test "Bitcask spec implementation: list" { + const bc = BitCask; + try bc.list(); +} + +test "Bitcask spec implementation: fold" { + const bc = BitCask; + try bc.fold(); +} + +test "Bitcask spec implementation: merge" { + const bc = BitCask; + try bc.merge(); +} + +test "Bitcask spec implementation: sync" { + const bc = BitCask; + try bc.merge(); +} + +test "Bitcask spec implementation: close" { + const bc = BitCask; + try bc.close(); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..c8a3f67 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,24 @@ +const std = @import("std"); + +pub fn main() !void { + // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) + std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + + // stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + const stdout_file = std.io.getStdOut().writer(); + var bw = std.io.bufferedWriter(stdout_file); + const stdout = bw.writer(); + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try bw.flush(); // don't forget to flush! +} + +test "simple test" { + var list = std.ArrayList(i32).init(std.testing.allocator); + defer list.deinit(); // try commenting this out and see if zig detects the memory leak! + try list.append(42); + try std.testing.expectEqual(@as(i32, 42), list.pop()); +}