ZipponDB/src/stuffs/filter.zig

390 lines
14 KiB
Zig

// 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),
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 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));
}