Deleted stuffs dir for a dataStructure one

This commit is contained in:
2025-01-11 23:41:03 +01:00
parent bc0c0cbe1d
commit d775ff1d0a
15 changed files with 67 additions and 36 deletions
+361
View File
@@ -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()});
}
+180
View File
@@ -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});
}
+46
View File
@@ -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) };
}
};
+31
View File
@@ -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));
}
};
}
+394
View File
@@ -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));
}
+74
View File
@@ -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;
}