fediglam/src/main/memdb.zig

170 lines
4.4 KiB
Zig

const std = @import("std");
const util = @import("util");
const Uuid = util.Uuid;
const models = @import("./models.zig");
// Clones a struct and its fields to a single layer of depth.
// Caller owns memory, can be freed using free below
// TODO: check that this is a struct, etc etc
fn clone(alloc: std.mem.Allocator, val: anytype) !@TypeOf(val) {
var result: @TypeOf(val) = undefined;
errdefer {
@panic("memory leak in deep clone, fix this");
}
inline for (std.meta.fields(@TypeOf(val))) |f| {
// TODO
if (f.field_type == []u8 or f.field_type == []const u8) {
@field(result, f.name) = try cloneString(alloc, @field(val, f.name));
} else if (f.field_type == Uuid) {
@field(result, f.name) = @field(val, f.name);
} else {
@compileError("unsupported field type " ++ @typeName(f.field_type));
}
}
return result;
}
fn cloneString(alloc: std.mem.Allocator, str: []const u8) ![]const u8 {
var result = try alloc.alloc(u8, str.len);
std.mem.copy(u8, result, str);
return result;
}
// Frees a struct and its fields returned by clone
pub fn free(alloc: std.mem.Allocator, val: anytype) void {
inline for (std.meta.fields(@TypeOf(val))) |f| {
// TODO
if (f.field_type == []u8 or f.field_type == []const u8) {
alloc.free(@field(val, f.name));
} else if (f.field_type == Uuid) {
// nothing
} else {
@compileError("unsupported field type " ++ @typeName(f.field_type));
}
}
}
pub fn Table(comptime T: type) type {
return struct {
const Self = @This();
internal_alloc: std.mem.Allocator,
data: std.AutoHashMap(Uuid, T),
pub fn init(alloc: std.mem.Allocator) !Self {
return Self{
.internal_alloc = alloc,
.data = std.AutoHashMap(Uuid, T).init(alloc),
};
}
pub fn deinit(self: *Self) void {
var iter = self.data.iterator();
while (iter.next()) |it| {
free(self.internal_alloc, it.value_ptr.*);
}
self.data.deinit();
}
pub fn contains(self: *Self, id: Uuid) !bool {
return self.data.contains(id);
}
// returns a copy of the note data from storage. memory is allocated with the provided
// allocator. can be freed using free() above
pub fn get(self: *Self, id: Uuid, alloc: std.mem.Allocator) !?T {
const data = self.data.get(id) orelse return null;
return try clone(alloc, data);
}
pub fn put(self: *Self, data: T) !void {
const copy = try clone(self.internal_alloc, data);
errdefer free(self.internal_alloc, copy);
const key = copy.id;
if (self.data.fetchRemove(key)) |e| {
free(self.internal_alloc, e.value);
}
try self.data.put(key, copy);
}
// TODO
pub fn lock(_: *Self) !void {
return;
}
pub fn unlock(_: *Self) void {
return;
}
};
}
pub const Database = struct {
internal_alloc: std.mem.Allocator,
notes: Table(models.Note),
users: Table(models.User),
pub fn init(alloc: std.mem.Allocator) !Database {
var db = Database{
.internal_alloc = alloc,
.notes = try Table(models.Note).init(alloc),
.users = try Table(models.User).init(alloc),
};
return db;
}
pub fn deinit(self: *Database) void {
self.notes.deinit();
self.users.deinit();
}
};
test "clone" {
const T = struct {
name: []const u8,
value: []const u8,
};
const copy = try clone(std.testing.allocator, T{ .name = "myName", .value = "myValue" });
free(std.testing.allocator, copy);
}
test "db" {
var db = try Database.init(std.testing.allocator);
defer db.deinit();
try db.putNote(.{
.id = "100",
.content = "content",
});
const note = (try db.getNote("100", std.testing.allocator)).?;
free(std.testing.allocator, note);
}
test "db" {
var db = try Database.init(std.testing.allocator);
defer db.deinit();
try db.putNote(.{
.id = "100",
.content = "content",
});
try db.putNote(.{
.id = "100",
.content = "content",
});
try db.putNote(.{
.id = "100",
.content = "content",
});
}