Deleted stuffs dir for a dataStructure one
This commit is contained in:
@@ -0,0 +1,361 @@
|
||||
const std = @import("std");
|
||||
const UUID = @import("dtype").UUID;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
|
||||
// TODO:
|
||||
// 1. Basic RadixTrie and Node - OK
|
||||
// 2. Add one UUID
|
||||
// 3. Get one file index using one UUID
|
||||
// 4. Get a list of file index using a list of UUID
|
||||
|
||||
const Node = union(enum) {
|
||||
branch: *std.StringHashMap(*Node),
|
||||
leaf: usize,
|
||||
|
||||
fn contains(self: Node, id: []const u8) bool {
|
||||
return switch (self) {
|
||||
.leaf => id.len == 0,
|
||||
.branch => |branch| {
|
||||
var longest_prefix: usize = 0;
|
||||
var longest_key: ?[]const u8 = null;
|
||||
|
||||
var it = branch.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const key = entry.key_ptr.*;
|
||||
const common_prefix = commonPrefix(key, id);
|
||||
if (common_prefix > longest_prefix) {
|
||||
longest_prefix = common_prefix;
|
||||
longest_key = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (longest_prefix == 0) {
|
||||
return false;
|
||||
} else if (longest_prefix == id.len and longest_key.?.len == id.len) {
|
||||
return true;
|
||||
} else if (longest_prefix < id.len) {
|
||||
const next_node = branch.get(longest_key.?).?;
|
||||
return next_node.contains(id[longest_prefix..]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn get(self: Node, id: []const u8) ?usize {
|
||||
switch (self) {
|
||||
.leaf => |leaf| {
|
||||
if (id.len == 0) return leaf;
|
||||
return null;
|
||||
},
|
||||
.branch => |branch| {
|
||||
var longest_prefix: usize = 0;
|
||||
var longest_key: ?[]const u8 = null;
|
||||
|
||||
var it = branch.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const key = entry.key_ptr.*;
|
||||
const common_prefix = commonPrefix(key, id);
|
||||
if (common_prefix > longest_prefix) {
|
||||
longest_prefix = common_prefix;
|
||||
longest_key = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (longest_prefix == 0) {
|
||||
return null;
|
||||
} else if (longest_prefix == id.len and longest_key.?.len == id.len) {
|
||||
return branch.get(id).?.leaf;
|
||||
} else if (longest_prefix < id.len) {
|
||||
const next_node = branch.get(longest_key.?).?;
|
||||
return next_node.get(id[longest_prefix..]);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(self: *Node, arena: *ArenaAllocator, id: []const u8, file_index: usize) !void {
|
||||
const allocator = arena.allocator();
|
||||
switch (self.*) {
|
||||
.leaf => {
|
||||
// If we're at a leaf, we need to create a new branch
|
||||
const new_branch = try allocator.create(std.StringHashMap(*Node));
|
||||
new_branch.* = std.StringHashMap(*Node).init(allocator);
|
||||
|
||||
// Move the current leaf to the new branch
|
||||
try new_branch.put("", self);
|
||||
|
||||
// Create a new leaf for the new UUID
|
||||
const new_leaf = try allocator.create(Node);
|
||||
new_leaf.* = Node{ .leaf = file_index };
|
||||
try new_branch.put(id, new_leaf);
|
||||
|
||||
// Update the current node to be a branch
|
||||
self.* = Node{ .branch = new_branch };
|
||||
},
|
||||
.branch => |branch| {
|
||||
var longest_prefix: usize = 0;
|
||||
var longest_key: ?[]const u8 = null;
|
||||
|
||||
// Find the longest common prefix
|
||||
var it = branch.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const key = entry.key_ptr.*;
|
||||
const common_prefix = commonPrefix(key, id);
|
||||
if (common_prefix > longest_prefix) {
|
||||
longest_prefix = common_prefix;
|
||||
longest_key = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (longest_prefix == 0) {
|
||||
// No common prefix, add a new leaf
|
||||
const new_leaf = try allocator.create(Node);
|
||||
new_leaf.* = Node{ .leaf = file_index };
|
||||
try branch.put(try allocator.dupe(u8, id), new_leaf);
|
||||
} else if (longest_prefix == id.len and longest_key.?.len == id.len) {
|
||||
// Exact match, update the leaf
|
||||
const existing_node = branch.get(longest_key.?).?;
|
||||
existing_node.* = Node{ .leaf = file_index };
|
||||
} else {
|
||||
// Partial match
|
||||
const common = id[0..longest_prefix];
|
||||
const existing_suffix = longest_key.?[longest_prefix..];
|
||||
const new_suffix = id[longest_prefix..];
|
||||
|
||||
if (!branch.contains(common)) {
|
||||
// Partial match dont exist, split the Node
|
||||
|
||||
// When I explain, I take example that 1000 is already in the branch and we add 1011
|
||||
// 1. Create a new Node branch with the common part of the UUID. This will be 10 in our situation
|
||||
const new_branch = try allocator.create(std.StringHashMap(*Node));
|
||||
new_branch.* = std.StringHashMap(*Node).init(allocator);
|
||||
|
||||
const new_node = try allocator.create(Node);
|
||||
new_node.* = Node{ .branch = new_branch };
|
||||
try branch.put(try allocator.dupe(u8, common), new_node);
|
||||
|
||||
// 2. Get the existing leaf key and add the end of the uuid to the new branch. E.g. 00, the last 0 bit of the existing key
|
||||
const existing_file_index = branch.get(longest_key.?).?;
|
||||
try new_branch.put(try allocator.dupe(u8, existing_suffix), existing_file_index);
|
||||
|
||||
// 3. Also add the new key, here 11
|
||||
const new_leaf = try allocator.create(Node);
|
||||
new_leaf.* = Node{ .leaf = file_index };
|
||||
try new_branch.put(try allocator.dupe(u8, new_suffix), new_leaf);
|
||||
|
||||
// 4. Delete the previous existing key
|
||||
const kv = branch.fetchRemove(longest_key.?);
|
||||
allocator.free(kv.?.key);
|
||||
allocator.destroy(kv.?.value);
|
||||
} else {
|
||||
// Partial match exist, add a leaf
|
||||
const new_leaf = try allocator.create(Node);
|
||||
new_leaf.* = Node{ .leaf = file_index };
|
||||
|
||||
var existing_node = branch.get(common).?;
|
||||
try existing_node.branch.put(try allocator.dupe(u8, new_suffix), new_leaf);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const RadixTrie = struct {
|
||||
arena: *ArenaAllocator,
|
||||
root_node: *Node,
|
||||
|
||||
fn init(allocator: std.mem.Allocator) !RadixTrie {
|
||||
const arena = try allocator.create(ArenaAllocator);
|
||||
errdefer allocator.destroy(arena);
|
||||
arena.* = ArenaAllocator.init(allocator);
|
||||
|
||||
const map = try arena.allocator().create(std.StringHashMap(*Node));
|
||||
map.* = std.StringHashMap(*Node).init(arena.allocator());
|
||||
|
||||
const node = try arena.allocator().create(Node);
|
||||
node.* = Node{ .branch = map };
|
||||
|
||||
return RadixTrie{
|
||||
.root_node = node,
|
||||
.arena = arena,
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: *RadixTrie) void {
|
||||
const allocator = self.arena.child_allocator;
|
||||
self.arena.deinit();
|
||||
allocator.destroy(self.arena);
|
||||
}
|
||||
|
||||
fn insert(self: *RadixTrie, uuid: UUID, file_index: usize) !void {
|
||||
try self.root_node.*.insert(self.arena, uuid.bytes[0..], file_index);
|
||||
}
|
||||
|
||||
fn contains(self: RadixTrie, uuid: UUID) bool {
|
||||
return self.root_node.contains(uuid.bytes[0..]);
|
||||
}
|
||||
|
||||
fn get(self: RadixTrie, uuid: UUID) ?usize {
|
||||
return self.root_node.get(uuid.bytes[0..]);
|
||||
}
|
||||
};
|
||||
|
||||
fn commonPrefix(a: []const u8, b: []const u8) usize {
|
||||
var i: usize = 0;
|
||||
while (i < a.len and i < b.len and a[i] == b[i]) : (i += 1) {}
|
||||
return i;
|
||||
}
|
||||
|
||||
test "Create empty RadixTrie" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var radix_trie = try RadixTrie.init(allocator);
|
||||
defer radix_trie.deinit();
|
||||
}
|
||||
|
||||
test "Get UUID in RadixTrie" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var radix_trie = try RadixTrie.init(allocator);
|
||||
defer radix_trie.deinit();
|
||||
|
||||
const uuid = try UUID.parse("00000000-0000-0000-0000-000000000000");
|
||||
|
||||
try radix_trie.insert(uuid, 0);
|
||||
const expected: usize = 0;
|
||||
try std.testing.expectEqual(radix_trie.get(uuid), expected);
|
||||
}
|
||||
|
||||
test "Update UUID in RadixTrie" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var radix_trie = try RadixTrie.init(allocator);
|
||||
defer radix_trie.deinit();
|
||||
|
||||
const uuid = try UUID.parse("00000000-0000-0000-0000-000000000000");
|
||||
|
||||
for (0..1000) |i| {
|
||||
try radix_trie.insert(uuid, i);
|
||||
try std.testing.expectEqual(radix_trie.get(uuid), i);
|
||||
}
|
||||
}
|
||||
|
||||
test "Splite Node RadixTrie" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var radix_trie = try RadixTrie.init(allocator);
|
||||
defer radix_trie.deinit();
|
||||
|
||||
const uuid0 = try UUID.parse("00000000-0000-0000-0000-000000000000");
|
||||
const uuid1 = try UUID.parse("00000000-0000-0000-0000-000000000001");
|
||||
const uuid2 = try UUID.parse("00000000-0000-0000-0000-000000000002");
|
||||
try radix_trie.insert(uuid0, 0);
|
||||
try radix_trie.insert(uuid1, 1);
|
||||
try radix_trie.insert(uuid2, 2);
|
||||
|
||||
try std.testing.expect(radix_trie.contains(uuid0));
|
||||
try std.testing.expect(radix_trie.contains(uuid1));
|
||||
try std.testing.expect(radix_trie.contains(uuid2));
|
||||
|
||||
const expected_values = [_]usize{ 0, 1, 2 };
|
||||
try std.testing.expectEqual(radix_trie.get(uuid0), expected_values[0]);
|
||||
try std.testing.expectEqual(radix_trie.get(uuid1), expected_values[1]);
|
||||
try std.testing.expectEqual(radix_trie.get(uuid2), expected_values[2]);
|
||||
}
|
||||
|
||||
test "Multiple Node RadixTrie with Deep Subdivisions" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var radix_trie = try RadixTrie.init(allocator);
|
||||
defer radix_trie.deinit();
|
||||
|
||||
const uuids = [_][]const u8{
|
||||
"00000000-0000-0000-0000-000000000000",
|
||||
"00000000-0000-0000-0000-000000000001",
|
||||
"00000000-0000-0000-0000-000000000002",
|
||||
"10000000-0000-0000-0000-000000000000",
|
||||
"11000000-0000-0000-0000-000000000000",
|
||||
"11100000-0000-0000-0000-000000000000",
|
||||
"11110000-0000-0000-0000-000000000000",
|
||||
"11111000-0000-0000-0000-000000000000",
|
||||
"11111100-0000-0000-0000-000000000000",
|
||||
"11111110-0000-0000-0000-000000000000",
|
||||
"11111111-0000-0000-0000-000000000000",
|
||||
};
|
||||
|
||||
// Insert UUIDs
|
||||
for (uuids, 0..) |uuid_str, i| {
|
||||
const uuid = try UUID.parse(uuid_str);
|
||||
try radix_trie.insert(uuid, i);
|
||||
}
|
||||
|
||||
// Test contains and get
|
||||
for (uuids, 0..) |uuid_str, i| {
|
||||
const uuid = try UUID.parse(uuid_str);
|
||||
try std.testing.expect(radix_trie.contains(uuid));
|
||||
try std.testing.expectEqual(radix_trie.get(uuid).?, i);
|
||||
}
|
||||
|
||||
// Test non-existent UUIDs
|
||||
const non_existent_uuids = [_][]const u8{
|
||||
"ffffffff-ffff-ffff-ffff-ffffffffffff",
|
||||
"22222222-2222-2222-2222-222222222222",
|
||||
"11111111-1111-1111-1111-111111111111",
|
||||
};
|
||||
|
||||
for (non_existent_uuids) |uuid_str| {
|
||||
const uuid = try UUID.parse(uuid_str);
|
||||
std.debug.print("{s}\n", .{uuid_str});
|
||||
try std.testing.expect(!radix_trie.contains(uuid));
|
||||
try std.testing.expectEqual(radix_trie.get(uuid), null);
|
||||
}
|
||||
|
||||
// Test partial matches
|
||||
const partial_matches = [_]struct { uuid: []const u8, expected_value: ?usize }{
|
||||
.{ .uuid = "00000000-0000-0000-0000-000000000003", .expected_value = null },
|
||||
.{ .uuid = "10000000-0000-0000-0000-000000000001", .expected_value = null },
|
||||
.{ .uuid = "11100000-0000-0000-0000-000000000001", .expected_value = null },
|
||||
.{ .uuid = "11111111-1000-0000-0000-000000000000", .expected_value = null },
|
||||
};
|
||||
|
||||
for (partial_matches) |pm| {
|
||||
const uuid = try UUID.parse(pm.uuid);
|
||||
try std.testing.expectEqual(pm.expected_value, radix_trie.get(uuid));
|
||||
}
|
||||
}
|
||||
|
||||
test "Radix benchmark insert" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var radix_trie = try RadixTrie.init(allocator);
|
||||
defer radix_trie.deinit();
|
||||
|
||||
for (0..10_000) |_| {
|
||||
const uuid = UUID.init();
|
||||
try radix_trie.insert(uuid, 0);
|
||||
_ = radix_trie.contains(uuid);
|
||||
}
|
||||
|
||||
std.debug.print("Memory use: {d}\n", .{radix_trie.arena.queryCapacity()});
|
||||
}
|
||||
|
||||
test "Hashmap benchmark" {
|
||||
const allocator = std.testing.allocator;
|
||||
var arena = ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
var map = std.AutoHashMap(UUID, usize).init(arena.allocator());
|
||||
|
||||
for (0..10_000) |_| {
|
||||
const uuid = UUID.init();
|
||||
try map.put(uuid, 0);
|
||||
_ = map.contains(uuid);
|
||||
}
|
||||
|
||||
std.debug.print("Memory use: {d}\n", .{arena.queryCapacity()});
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
const std = @import("std");
|
||||
const UUID = @import("dtype").UUID;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
|
||||
pub const UUIDIndexMap = @This();
|
||||
|
||||
arena: *ArenaAllocator,
|
||||
map: *std.AutoHashMap(UUID, usize),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !UUIDIndexMap {
|
||||
const arena = try allocator.create(ArenaAllocator);
|
||||
errdefer allocator.destroy(arena);
|
||||
arena.* = ArenaAllocator.init(allocator);
|
||||
|
||||
const map = try arena.allocator().create(std.AutoHashMap(UUID, usize));
|
||||
map.* = std.AutoHashMap(UUID, usize).init(arena.allocator());
|
||||
|
||||
return UUIDIndexMap{
|
||||
.map = map,
|
||||
.arena = arena,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *UUIDIndexMap) void {
|
||||
const allocator = self.arena.child_allocator;
|
||||
self.arena.deinit();
|
||||
allocator.destroy(self.arena);
|
||||
}
|
||||
|
||||
pub fn put(self: *UUIDIndexMap, uuid: UUID, file_index: usize) !void {
|
||||
const allocator = self.arena.allocator();
|
||||
const new_uuid = try allocator.create(UUID);
|
||||
new_uuid.* = uuid;
|
||||
|
||||
const new_file_index = try allocator.create(usize);
|
||||
new_file_index.* = file_index;
|
||||
|
||||
try self.map.*.put(new_uuid.*, new_file_index.*);
|
||||
}
|
||||
|
||||
pub fn contains(self: UUIDIndexMap, uuid: UUID) bool {
|
||||
return self.map.contains(uuid);
|
||||
}
|
||||
|
||||
pub fn get(self: UUIDIndexMap, uuid: UUID) ?usize {
|
||||
return self.map.get(uuid);
|
||||
}
|
||||
|
||||
test "Create empty UUIDIndexMap" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var imap = try UUIDIndexMap.init(allocator);
|
||||
defer imap.deinit();
|
||||
}
|
||||
|
||||
test "Get UUID in UUIDIndexMap" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var imap = try UUIDIndexMap.init(allocator);
|
||||
defer imap.deinit();
|
||||
|
||||
const uuid = try UUID.parse("00000000-0000-0000-0000-000000000000");
|
||||
|
||||
try imap.put(uuid, 0);
|
||||
const expected: usize = 0;
|
||||
try std.testing.expectEqual(imap.get(uuid), expected);
|
||||
}
|
||||
|
||||
test "Update UUID in UUIDIndexMap" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var imap = try UUIDIndexMap.init(allocator);
|
||||
defer imap.deinit();
|
||||
|
||||
const uuid = try UUID.parse("00000000-0000-0000-0000-000000000000");
|
||||
|
||||
for (0..1000) |i| {
|
||||
try imap.put(uuid, i);
|
||||
try std.testing.expectEqual(imap.get(uuid), i);
|
||||
}
|
||||
}
|
||||
|
||||
test "UUIDIndexMap multiple keys" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var imap = try UUIDIndexMap.init(allocator);
|
||||
defer imap.deinit();
|
||||
|
||||
const uuid0 = try UUID.parse("00000000-0000-0000-0000-000000000000");
|
||||
const uuid1 = try UUID.parse("00000000-0000-0000-0000-000000000001");
|
||||
const uuid2 = try UUID.parse("00000000-0000-0000-0000-000000000002");
|
||||
try imap.put(uuid0, 0);
|
||||
try imap.put(uuid1, 1);
|
||||
try imap.put(uuid2, 2);
|
||||
|
||||
try std.testing.expect(imap.contains(uuid0));
|
||||
try std.testing.expect(imap.contains(uuid1));
|
||||
try std.testing.expect(imap.contains(uuid2));
|
||||
|
||||
const expected_values = [_]usize{ 0, 1, 2 };
|
||||
try std.testing.expectEqual(imap.get(uuid0), expected_values[0]);
|
||||
try std.testing.expectEqual(imap.get(uuid1), expected_values[1]);
|
||||
try std.testing.expectEqual(imap.get(uuid2), expected_values[2]);
|
||||
}
|
||||
|
||||
test "Multiple Node UUIDIndexMap with Deep Subdivisions" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var imap = try UUIDIndexMap.init(allocator);
|
||||
defer imap.deinit();
|
||||
|
||||
const uuids = [_][]const u8{
|
||||
"00000000-0000-0000-0000-000000000000",
|
||||
"00000000-0000-0000-0000-000000000001",
|
||||
"00000000-0000-0000-0000-000000000002",
|
||||
"10000000-0000-0000-0000-000000000000",
|
||||
"11000000-0000-0000-0000-000000000000",
|
||||
"11100000-0000-0000-0000-000000000000",
|
||||
"11110000-0000-0000-0000-000000000000",
|
||||
"11111000-0000-0000-0000-000000000000",
|
||||
"11111100-0000-0000-0000-000000000000",
|
||||
"11111110-0000-0000-0000-000000000000",
|
||||
"11111111-0000-0000-0000-000000000000",
|
||||
};
|
||||
|
||||
// Insert UUIDs
|
||||
for (uuids, 0..) |uuid_str, i| {
|
||||
const uuid = try UUID.parse(uuid_str);
|
||||
try imap.put(uuid, i);
|
||||
}
|
||||
|
||||
// Test contains and get
|
||||
for (uuids, 0..) |uuid_str, i| {
|
||||
const uuid = try UUID.parse(uuid_str);
|
||||
try std.testing.expect(imap.contains(uuid));
|
||||
try std.testing.expectEqual(imap.get(uuid).?, i);
|
||||
}
|
||||
|
||||
// Test non-existent UUIDs
|
||||
const non_existent_uuids = [_][]const u8{
|
||||
"ffffffff-ffff-ffff-ffff-ffffffffffff",
|
||||
"22222222-2222-2222-2222-222222222222",
|
||||
"11111111-1111-1111-1111-111111111111",
|
||||
};
|
||||
|
||||
for (non_existent_uuids) |uuid_str| {
|
||||
const uuid = try UUID.parse(uuid_str);
|
||||
try std.testing.expect(!imap.contains(uuid));
|
||||
try std.testing.expectEqual(imap.get(uuid), null);
|
||||
}
|
||||
|
||||
// Test partial matches
|
||||
const partial_matches = [_]struct { uuid: []const u8, expected_value: ?usize }{
|
||||
.{ .uuid = "00000000-0000-0000-0000-000000000003", .expected_value = null },
|
||||
.{ .uuid = "10000000-0000-0000-0000-000000000001", .expected_value = null },
|
||||
.{ .uuid = "11100000-0000-0000-0000-000000000001", .expected_value = null },
|
||||
.{ .uuid = "11111111-1000-0000-0000-000000000000", .expected_value = null },
|
||||
};
|
||||
|
||||
for (partial_matches) |pm| {
|
||||
const uuid = try UUID.parse(pm.uuid);
|
||||
try std.testing.expectEqual(pm.expected_value, imap.get(uuid));
|
||||
}
|
||||
}
|
||||
|
||||
test "UUIDIndexMap benchmark" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var imap = try UUIDIndexMap.init(allocator);
|
||||
defer imap.deinit();
|
||||
|
||||
for (0..1_000_000) |_| {
|
||||
const uuid = UUID.init();
|
||||
try imap.put(uuid, 0);
|
||||
_ = imap.contains(uuid);
|
||||
}
|
||||
|
||||
const mb: f64 = @as(f64, @floatFromInt(imap.arena.queryCapacity())) / 1024.0 / 1024.0;
|
||||
std.debug.print("Memory use for 1 000 000: {d}MB\n", .{mb});
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const RelationMap = @import("relationMap.zig").RelationMap;
|
||||
const dtype = @import("dtype");
|
||||
const DataType = dtype.DataType;
|
||||
|
||||
// TODO: Put this in a data structure directory
|
||||
|
||||
const ZipponError = @import("../errors.zig").ZipponError;
|
||||
|
||||
/// This is the [] part
|
||||
pub const AdditionalData = @This();
|
||||
|
||||
allocator: Allocator,
|
||||
limit: usize = 0,
|
||||
childrens: std.ArrayList(AdditionalDataMember),
|
||||
|
||||
pub fn init(allocator: Allocator) AdditionalData {
|
||||
return AdditionalData{
|
||||
.allocator = allocator,
|
||||
.childrens = std.ArrayList(AdditionalDataMember).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn populateWithEverythingExceptLink(self: *AdditionalData, members: [][]const u8, dtypes: []DataType) !void {
|
||||
for (members, dtypes, 0..) |member, dt, i| {
|
||||
if (dt == .link or dt == .link_array) continue;
|
||||
try self.childrens.append(AdditionalDataMember.init(self.allocator, member, i));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addMember(self: *AdditionalData, name: []const u8, index: usize) ZipponError!void {
|
||||
self.childrens.append(AdditionalDataMember.init(self.allocator, name, index)) catch return ZipponError.MemoryError;
|
||||
}
|
||||
|
||||
// This is name in: [name]
|
||||
// There is an additional data because it can be [friend [1; name]]
|
||||
pub const AdditionalDataMember = struct {
|
||||
name: []const u8,
|
||||
index: usize, // Index place in the schema
|
||||
additional_data: AdditionalData,
|
||||
|
||||
pub fn init(allocator: Allocator, name: []const u8, index: usize) AdditionalDataMember {
|
||||
return AdditionalDataMember{ .name = name, .index = index, .additional_data = AdditionalData.init(allocator) };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// For when I move to a single file
|
||||
// How I see it is that I have some kind of fileVar that keep a position and type
|
||||
// When I want to read the value, I read the file then convert it back
|
||||
// I can do something similar but with a chunck of data after
|
||||
// Like a start position and size, like memory but for file
|
||||
// The point being giving more size when I modify thing to prevent rewritting everything
|
||||
|
||||
const std = @import("std");
|
||||
const zid = @import("ZipponData");
|
||||
|
||||
pub fn fileVar(comptime T: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
position: usize,
|
||||
file: *std.fs.File,
|
||||
T: type = T,
|
||||
|
||||
pub fn init(position: usize, file: *std.fs.File) Self {
|
||||
return .{
|
||||
.position = position,
|
||||
.file = file,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn read(self: Self) !T {
|
||||
try self.file.seekTo(self.position);
|
||||
self.file.reader().readBytesNoEof(@sizeOf(T));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
// Should do a tree like that:
|
||||
// AND
|
||||
// / \
|
||||
// OR OR
|
||||
// / \ / \
|
||||
// name name age age
|
||||
// ='A' ='B' >80 <20
|
||||
//
|
||||
// For {(name = 'Adrien' OR name = 'Bob') AND (age > 80 OR age < 20)}
|
||||
|
||||
const std = @import("std");
|
||||
const s2t = @import("dtype").s2t;
|
||||
const ZipponError = @import("../errors.zig").ZipponError;
|
||||
const DataType = @import("dtype").DataType;
|
||||
const DateTime = @import("dtype").DateTime;
|
||||
const UUID = @import("dtype").UUID;
|
||||
const Data = @import("ZipponData").Data;
|
||||
|
||||
const log = std.log.scoped(.filter);
|
||||
|
||||
pub const ComparisonOperator = enum {
|
||||
equal,
|
||||
different,
|
||||
superior,
|
||||
superior_or_equal,
|
||||
inferior,
|
||||
inferior_or_equal,
|
||||
in,
|
||||
not_in,
|
||||
|
||||
pub fn str(self: ComparisonOperator) []const u8 {
|
||||
return switch (self) {
|
||||
.equal => "=",
|
||||
.different => "!=",
|
||||
.superior => ">",
|
||||
.superior_or_equal => ">=",
|
||||
.inferior => "<",
|
||||
.inferior_or_equal => "<=",
|
||||
.in => "IN",
|
||||
.not_in => "!IN",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const LogicalOperator = enum {
|
||||
AND,
|
||||
OR,
|
||||
|
||||
pub fn str(self: LogicalOperator) []const u8 {
|
||||
return switch (self) {
|
||||
.AND => "AND",
|
||||
.OR => "OR",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const ConditionValue = union(enum) {
|
||||
int: i32,
|
||||
float: f64,
|
||||
str: []const u8,
|
||||
bool_: bool,
|
||||
self: UUID,
|
||||
unix: u64,
|
||||
int_array: []const i32,
|
||||
str_array: []const []const u8,
|
||||
float_array: []const f64,
|
||||
bool_array: []const bool,
|
||||
unix_array: []const u64,
|
||||
link: *std.AutoHashMap(UUID, void),
|
||||
link_array: *std.AutoHashMap(UUID, void),
|
||||
|
||||
pub fn initInt(value: []const u8) ConditionValue {
|
||||
return ConditionValue{ .int = s2t.parseInt(value) };
|
||||
}
|
||||
|
||||
pub fn initFloat(value: []const u8) ConditionValue {
|
||||
return ConditionValue{ .float = s2t.parseFloat(value) };
|
||||
}
|
||||
|
||||
pub fn initStr(value: []const u8) ConditionValue {
|
||||
return ConditionValue{ .str = value };
|
||||
}
|
||||
|
||||
pub fn initSelf(value: UUID) ConditionValue {
|
||||
return ConditionValue{ .self = value };
|
||||
}
|
||||
|
||||
pub fn initBool(value: []const u8) ConditionValue {
|
||||
return ConditionValue{ .bool_ = s2t.parseBool(value) };
|
||||
}
|
||||
|
||||
pub fn initDate(value: []const u8) ConditionValue {
|
||||
return ConditionValue{ .unix = s2t.parseDate(value).toUnix() };
|
||||
}
|
||||
|
||||
pub fn initTime(value: []const u8) ConditionValue {
|
||||
return ConditionValue{ .unix = s2t.parseTime(value).toUnix() };
|
||||
}
|
||||
|
||||
pub fn initDateTime(value: []const u8) ConditionValue {
|
||||
return ConditionValue{ .unix = s2t.parseDatetime(value).toUnix() };
|
||||
}
|
||||
|
||||
// Array
|
||||
pub fn initArrayInt(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
|
||||
return ConditionValue{ .int_array = s2t.parseArrayInt(allocator, value) catch return ZipponError.ParsingValueError };
|
||||
}
|
||||
|
||||
pub fn initArrayFloat(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
|
||||
return ConditionValue{ .float_array = s2t.parseArrayFloat(allocator, value) catch return ZipponError.ParsingValueError };
|
||||
}
|
||||
|
||||
pub fn initArrayStr(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
|
||||
return ConditionValue{ .str_array = s2t.parseArrayStr(allocator, value) catch return ZipponError.ParsingValueError };
|
||||
}
|
||||
|
||||
pub fn initArrayBool(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
|
||||
return ConditionValue{ .bool_array = s2t.parseArrayBool(allocator, value) catch return ZipponError.ParsingValueError };
|
||||
}
|
||||
|
||||
pub fn initArrayDate(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
|
||||
return ConditionValue{ .unix_array = s2t.parseArrayDateUnix(allocator, value) catch return ZipponError.ParsingValueError };
|
||||
}
|
||||
|
||||
pub fn initArrayTime(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
|
||||
return ConditionValue{ .unix_array = s2t.parseArrayTimeUnix(allocator, value) catch return ZipponError.ParsingValueError };
|
||||
}
|
||||
|
||||
pub fn initArrayDateTime(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
|
||||
return ConditionValue{ .unix_array = s2t.parseArrayDatetimeUnix(allocator, value) catch return ZipponError.ParsingValueError };
|
||||
}
|
||||
|
||||
pub fn initLink(value: *std.AutoHashMap(UUID, void)) ConditionValue {
|
||||
return ConditionValue{ .link = value };
|
||||
}
|
||||
|
||||
pub fn initArrayLink(value: *std.AutoHashMap(UUID, void)) ConditionValue {
|
||||
return ConditionValue{ .link_array = value };
|
||||
}
|
||||
};
|
||||
|
||||
pub const Condition = struct {
|
||||
value: ConditionValue = undefined,
|
||||
operation: ComparisonOperator = undefined,
|
||||
data_type: DataType = undefined,
|
||||
data_index: usize = undefined, // Index in the file
|
||||
};
|
||||
|
||||
const FilterNode = union(enum) {
|
||||
condition: Condition,
|
||||
logical: struct {
|
||||
operator: LogicalOperator,
|
||||
left: *FilterNode,
|
||||
right: *FilterNode,
|
||||
},
|
||||
empty: bool,
|
||||
};
|
||||
|
||||
pub const Filter = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
root: *FilterNode,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) ZipponError!Filter {
|
||||
const node = allocator.create(FilterNode) catch return ZipponError.MemoryError;
|
||||
node.* = FilterNode{ .empty = true };
|
||||
return .{ .allocator = allocator, .root = node };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Filter) void {
|
||||
switch (self.root.*) {
|
||||
.logical => self.freeNode(self.root),
|
||||
else => {},
|
||||
}
|
||||
self.allocator.destroy(self.root);
|
||||
}
|
||||
|
||||
fn freeNode(self: *Filter, node: *FilterNode) void {
|
||||
switch (node.*) {
|
||||
.logical => |logical| {
|
||||
self.freeNode(logical.left);
|
||||
self.freeNode(logical.right);
|
||||
self.allocator.destroy(logical.left);
|
||||
self.allocator.destroy(logical.right);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addCondition(self: *Filter, condition: Condition) ZipponError!void {
|
||||
const node = self.allocator.create(FilterNode) catch return ZipponError.MemoryError;
|
||||
node.* = FilterNode{ .condition = condition };
|
||||
switch (self.root.*) {
|
||||
.empty => {
|
||||
self.allocator.destroy(self.root);
|
||||
self.root = node;
|
||||
},
|
||||
.logical => {
|
||||
var current = self.root;
|
||||
var founded = false;
|
||||
while (!founded) switch (current.logical.right.*) {
|
||||
.empty => founded = true,
|
||||
.logical => {
|
||||
current = current.logical.right;
|
||||
founded = false;
|
||||
},
|
||||
.condition => unreachable,
|
||||
};
|
||||
self.allocator.destroy(current.logical.right);
|
||||
current.logical.right = node;
|
||||
},
|
||||
.condition => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addLogicalOperator(self: *Filter, operator: LogicalOperator) ZipponError!void {
|
||||
const empty_node = self.allocator.create(FilterNode) catch return ZipponError.MemoryError;
|
||||
empty_node.* = FilterNode{ .empty = true };
|
||||
|
||||
const node = self.allocator.create(FilterNode) catch return ZipponError.MemoryError;
|
||||
node.* = FilterNode{ .logical = .{ .operator = operator, .left = self.root, .right = empty_node } };
|
||||
self.root = node;
|
||||
}
|
||||
|
||||
pub fn addSubFilter(self: *Filter, sub_filter: *Filter) void {
|
||||
switch (self.root.*) {
|
||||
.empty => {
|
||||
self.allocator.destroy(self.root);
|
||||
self.root = sub_filter.root;
|
||||
},
|
||||
.logical => {
|
||||
var current = self.root;
|
||||
var founded = false;
|
||||
while (!founded) switch (current.logical.right.*) {
|
||||
.empty => founded = true,
|
||||
.logical => {
|
||||
current = current.logical.right;
|
||||
founded = false;
|
||||
},
|
||||
.condition => unreachable,
|
||||
};
|
||||
self.allocator.destroy(current.logical.right);
|
||||
current.logical.right = sub_filter.root;
|
||||
},
|
||||
.condition => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use []Data and make it work
|
||||
pub fn evaluate(self: Filter, row: []Data) bool {
|
||||
return self.evaluateNode(self.root, row);
|
||||
}
|
||||
|
||||
fn evaluateNode(self: Filter, node: *FilterNode, row: []Data) bool {
|
||||
return switch (node.*) {
|
||||
.condition => |cond| Filter.evaluateCondition(cond, row[cond.data_index]),
|
||||
.logical => |logical| switch (logical.operator) {
|
||||
.AND => self.evaluateNode(logical.left, row) and self.evaluateNode(logical.right, row),
|
||||
.OR => self.evaluateNode(logical.left, row) or self.evaluateNode(logical.right, row),
|
||||
},
|
||||
.empty => true,
|
||||
};
|
||||
}
|
||||
|
||||
fn evaluateCondition(condition: Condition, row_value: Data) bool {
|
||||
return switch (condition.operation) {
|
||||
.equal => switch (condition.data_type) {
|
||||
.int => row_value.Int == condition.value.int,
|
||||
.float => row_value.Float == condition.value.float,
|
||||
.str => std.mem.eql(u8, row_value.Str, condition.value.str),
|
||||
.bool => row_value.Bool == condition.value.bool_,
|
||||
.date, .time, .datetime => row_value.Unix == condition.value.unix,
|
||||
else => unreachable,
|
||||
},
|
||||
|
||||
.different => switch (condition.data_type) {
|
||||
.int => row_value.Int != condition.value.int,
|
||||
.float => row_value.Float != condition.value.float,
|
||||
.str => !std.mem.eql(u8, row_value.Str, condition.value.str),
|
||||
.bool => row_value.Bool != condition.value.bool_,
|
||||
.date, .time, .datetime => row_value.Unix != condition.value.unix,
|
||||
else => unreachable,
|
||||
},
|
||||
|
||||
.superior_or_equal => switch (condition.data_type) {
|
||||
.int => row_value.Int >= condition.value.int,
|
||||
.float => row_value.Float >= condition.value.float,
|
||||
.date, .time, .datetime => row_value.Unix >= condition.value.unix,
|
||||
else => unreachable,
|
||||
},
|
||||
|
||||
.superior => switch (condition.data_type) {
|
||||
.int => row_value.Int > condition.value.int,
|
||||
.float => row_value.Float > condition.value.float,
|
||||
.date, .time, .datetime => row_value.Unix > condition.value.unix,
|
||||
else => unreachable,
|
||||
},
|
||||
|
||||
.inferior_or_equal => switch (condition.data_type) {
|
||||
.int => row_value.Int <= condition.value.int,
|
||||
.float => row_value.Float <= condition.value.float,
|
||||
.date, .time, .datetime => row_value.Unix <= condition.value.unix,
|
||||
else => unreachable,
|
||||
},
|
||||
|
||||
.inferior => switch (condition.data_type) {
|
||||
.int => row_value.Int < condition.value.int,
|
||||
.float => row_value.Float < condition.value.float,
|
||||
.date, .time, .datetime => row_value.Unix < condition.value.unix,
|
||||
else => unreachable,
|
||||
},
|
||||
|
||||
.in => switch (condition.data_type) {
|
||||
.link => condition.value.link.contains(UUID{ .bytes = row_value.UUID }),
|
||||
else => unreachable,
|
||||
},
|
||||
|
||||
.not_in => switch (condition.data_type) {
|
||||
.link => !condition.value.link.contains(UUID{ .bytes = row_value.UUID }),
|
||||
else => unreachable,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn debugPrint(self: Filter) void {
|
||||
self.printNode(self.root.*);
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
|
||||
fn printNode(self: Filter, node: FilterNode) void {
|
||||
switch (node) {
|
||||
.logical => |logical| {
|
||||
std.debug.print(" ( ", .{});
|
||||
self.printNode(logical.left.*);
|
||||
std.debug.print(" {s} ", .{logical.operator.str()});
|
||||
self.printNode(logical.right.*);
|
||||
std.debug.print(" ) ", .{});
|
||||
},
|
||||
.condition => |condition| std.debug.print("{d} {s} {any} |{any}|", .{
|
||||
condition.data_index,
|
||||
condition.operation.str(),
|
||||
condition.value,
|
||||
condition.data_type,
|
||||
}),
|
||||
.empty => std.debug.print("Empty", .{}),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test "Evaluate" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var data = [_]Data{
|
||||
Data.initInt(1),
|
||||
Data.initFloat(3.14159),
|
||||
Data.initInt(-5),
|
||||
Data.initStr("Hello world"),
|
||||
Data.initBool(true),
|
||||
};
|
||||
|
||||
var filter = try Filter.init(allocator);
|
||||
defer filter.deinit();
|
||||
|
||||
try filter.addCondition(Condition{ .value = ConditionValue.initInt("1"), .data_index = 0, .operation = .equal, .data_type = .int });
|
||||
|
||||
filter.debugPrint();
|
||||
|
||||
_ = filter.evaluate(&data);
|
||||
}
|
||||
|
||||
test "ConditionValue: link" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
// Create a hash map for storing UUIDs
|
||||
var hash_map = std.AutoHashMap(UUID, void).init(allocator);
|
||||
defer hash_map.deinit();
|
||||
|
||||
// Create a UUID to add to the hash map
|
||||
const uuid1 = try UUID.parse("123e4567-e89b-12d3-a456-426614174000");
|
||||
const uuid2 = try UUID.parse("223e4567-e89b-12d3-a456-426614174000");
|
||||
|
||||
// Add UUIDs to the hash map
|
||||
try hash_map.put(uuid1, {});
|
||||
try hash_map.put(uuid2, {});
|
||||
|
||||
// Create a ConditionValue with the link
|
||||
var value = ConditionValue.initLink(&hash_map);
|
||||
|
||||
// Check that the hash map contains the correct number of UUIDs
|
||||
try std.testing.expectEqual(@as(usize, 2), value.link.count());
|
||||
|
||||
// Check that specific UUIDs are in the hash map
|
||||
try std.testing.expect(value.link.contains(uuid1));
|
||||
try std.testing.expect(value.link.contains(uuid2));
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// A relation map is all data needed to add relationship at the during parsing
|
||||
// How it work is that, the first time I parse the struct files, like User, I populate a map of UUID empty string
|
||||
// And in the JSON string I write {|<[16]u8>|} inside. Then I can use this struct to parse the file again
|
||||
// And if the UUID is in the map, I write the JSON if in its value in the map
|
||||
// It need to be recurcive as additional data can do stuff like [name, friends [name, best_friend]]
|
||||
// I could use parseEntities in a recursive way. But that mean ready the file at each loop =/
|
||||
//
|
||||
// No no no, But on the other hands that would solve the issue of getting the UUID of the best_friend
|
||||
// Fuck thats true, I didnt think about that. I can one populate the UUID that I want from the current depth of the additional data
|
||||
// So I need to parse multiple time. But that sove when using with multiple parse.
|
||||
// Because GRAB User [comments [post]]. How do I get the UUID of the Post if I only parse User ?
|
||||
//
|
||||
// Ok so I need to go recursive on parseEntities
|
||||
// So I parse one time, if additional data has relationship, I create a list of RelationMap
|
||||
// When I parse, I populate RelationMap with UUID I want
|
||||
// Then for each RelationMap, I parse the files again this time to update the first JSON that now have {|<>|}
|
||||
// With a sub additionalData. If there is an additional data relation, I recurcive.
|
||||
// So I need an option in parseEntity to either write the first JSON or update the existing one
|
||||
//
|
||||
// FIXME: I think if 2 different struct have the same member name it can cause issue but maybe not tho
|
||||
|
||||
const std = @import("std");
|
||||
const AdditionalData = @import("additionalData.zig").AdditionalData;
|
||||
const ZipponError = @import("../errors.zig").ZipponError;
|
||||
|
||||
pub const JsonString = struct {
|
||||
slice: []const u8 = "",
|
||||
init: bool = false,
|
||||
};
|
||||
|
||||
pub const RelationMap = @This();
|
||||
struct_name: []const u8,
|
||||
member_name: []const u8,
|
||||
additional_data: AdditionalData,
|
||||
map: *std.AutoHashMap([16]u8, JsonString),
|
||||
|
||||
/// Will use a string in the JSON format and look for {|<[16]u8>|}
|
||||
/// It will then check if it is for the right member name and if so, add an empty JSON string at the key
|
||||
pub fn populate(self: *RelationMap, input: []const u8) ZipponError!void {
|
||||
var uuid_bytes: [16]u8 = undefined;
|
||||
var start: usize = 0;
|
||||
while (std.mem.indexOf(u8, input[start..], "{|<")) |pos| {
|
||||
const pattern_start = start + pos + 3;
|
||||
const pattern_end = pattern_start + 16;
|
||||
|
||||
const member_end = if (input[pattern_start - 4] == '[') pattern_start - 6 else pattern_start - 5; // This should be ": {|<"
|
||||
var member_start = member_end - 1;
|
||||
while (input[member_start] != ' ') : (member_start -= 1) {}
|
||||
member_start += 1;
|
||||
|
||||
if (!std.mem.eql(u8, input[member_start..member_end], self.member_name)) continue;
|
||||
|
||||
if (input[pattern_start - 4] == '[') {
|
||||
start = try self.populateArray(input, pattern_start - 3);
|
||||
continue;
|
||||
}
|
||||
|
||||
@memcpy(uuid_bytes[0..], input[pattern_start..pattern_end]);
|
||||
|
||||
self.map.put(uuid_bytes, JsonString{}) catch return ZipponError.MemoryError;
|
||||
start = pattern_end + 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Array are pack in format {|<[16]u8>|},{|<[16]u8>|},{|<[16]u8>|},{|<[16]u8>|},
|
||||
fn populateArray(self: *RelationMap, input: []const u8, origin: usize) ZipponError!usize {
|
||||
var uuid_bytes: [16]u8 = undefined;
|
||||
var start = origin;
|
||||
while (input.len > start + 23 and std.mem.eql(u8, input[start .. start + 3], "{|<") and std.mem.eql(u8, input[start + 19 .. start + 23], ">|},")) : (start += 23) {
|
||||
for (start + 3..start + 19, 0..) |i, j| uuid_bytes[j] = input[i];
|
||||
self.map.put(uuid_bytes, JsonString{}) catch return ZipponError.MemoryError;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
Reference in New Issue
Block a user