const std = @import("std"); const zid = @import("ZipponData"); const Allocator = std.mem.Allocator; const Parser = @import("schemaParser.zig"); const Tokenizer = @import("tokenizers/schema.zig").Tokenizer; const ZipponError = @import("errors.zig").ZipponError; const dtype = @import("dtype"); const DataType = dtype.DataType; const AdditionalData = @import("dataStructure/additionalData.zig"); const RelationMap = @import("dataStructure/relationMap.zig"); const JsonString = @import("dataStructure/relationMap.zig").JsonString; const ConditionValue = @import("dataStructure/filter.zig").ConditionValue; const UUID = dtype.UUID; const UUIDFileIndex = @import("dataStructure/UUIDFileIndex.zig"); const FileEngine = @import("fileEngine.zig"); // TODO: Create a schemaEngine directory and add this as core and the parser with it const config = @import("config"); const BUFFER_SIZE = config.BUFFER_SIZE; var schema_buffer: [BUFFER_SIZE]u8 = undefined; // TODO: Stop keeping the allocator at the root of the file var arena: std.heap.ArenaAllocator = undefined; var allocator: Allocator = undefined; const log = std.log.scoped(.schemaEngine); pub const SchemaStruct = struct { name: []const u8, members: [][]const u8, types: []DataType, zid_schema: []zid.DType, links: std.StringHashMap([]const u8), // Map key as member_name and value as struct_name of the link uuid_file_index: *UUIDFileIndex, // Map UUID to the index of the file store in pub fn init( name: []const u8, members: [][]const u8, types: []DataType, links: std.StringHashMap([]const u8), ) ZipponError!SchemaStruct { const uuid_file_index = allocator.create(UUIDFileIndex) catch return ZipponError.MemoryError; uuid_file_index.* = UUIDFileIndex.init(allocator) catch return ZipponError.MemoryError; return SchemaStruct{ .name = name, .members = members, .types = types, .zid_schema = SchemaStruct.fileDataSchema(types) catch return ZipponError.MemoryError, .links = links, .uuid_file_index = uuid_file_index, }; } fn fileDataSchema(dtypes: []DataType) ZipponError![]zid.DType { var schema = std.ArrayList(zid.DType).init(allocator); for (dtypes) |dt| { schema.append(switch (dt) { .int => .Int, .float => .Float, .str => .Str, .bool => .Bool, .link => .UUID, .self => .UUID, .date => .Unix, .time => .Unix, .datetime => .Unix, .int_array => .IntArray, .float_array => .FloatArray, .str_array => .StrArray, .bool_array => .BoolArray, .date_array => .UnixArray, .time_array => .UnixArray, .datetime_array => .UnixArray, .link_array => .UUIDArray, }) catch return ZipponError.MemoryError; } return schema.toOwnedSlice() catch return ZipponError.MemoryError; } }; /// Manage everything that is relate to the schema /// This include keeping in memory the schema and schema file, and some functions to get like all members of a specific struct. /// For now it is a bit empty. But this is where I will manage migration pub const SchemaEngine = @This(); struct_array: []SchemaStruct, null_terminated: [:0]u8, // The path is the path to the schema file pub fn init(path: []const u8, file_engine: *FileEngine) ZipponError!SchemaEngine { arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); allocator = arena.allocator(); var buffer: [BUFFER_SIZE]u8 = undefined; log.debug("Trying to init a SchemaEngine with path {s}", .{path}); const len: usize = try FileEngine.readSchemaFile(path, &buffer); const null_terminated = std.fmt.bufPrintZ(&schema_buffer, "{s}", .{buffer[0..len]}) catch return ZipponError.MemoryError; var toker = Tokenizer.init(null_terminated); var parser = Parser.init(&toker, allocator); var struct_array = std.ArrayList(SchemaStruct).init(allocator); errdefer struct_array.deinit(); parser.parse(&struct_array) catch return ZipponError.SchemaNotConform; log.debug("SchemaEngine init with {d} SchemaStruct.", .{struct_array.items.len}); for (struct_array.items) |sstruct| { file_engine.populateFileIndexUUIDMap(sstruct, sstruct.uuid_file_index) catch |err| { log.err("Error populate file index UUID map {any}", .{err}); }; } return SchemaEngine{ .struct_array = struct_array.toOwnedSlice() catch return ZipponError.MemoryError, .null_terminated = null_terminated, }; } pub fn deinit(_: SchemaEngine) void { arena.deinit(); } /// Get the type of the member pub fn memberName2DataType(self: *SchemaEngine, struct_name: []const u8, member_name: []const u8) ZipponError!DataType { for (try self.structName2structMembers(struct_name), 0..) |mn, i| { const dtypes = try self.structName2DataType(struct_name); if (std.mem.eql(u8, mn, member_name)) return dtypes[i]; } return ZipponError.MemberNotFound; } pub fn memberName2DataIndex(self: *SchemaEngine, struct_name: []const u8, member_name: []const u8) ZipponError!usize { for (try self.structName2structMembers(struct_name), 0..) |mn, i| { if (std.mem.eql(u8, mn, member_name)) return i; } return ZipponError.MemberNotFound; } /// Get the list of all member name for a struct name pub fn structName2structMembers(self: SchemaEngine, struct_name: []const u8) ZipponError![][]const u8 { var i: usize = 0; while (i < self.struct_array.len) : (i += 1) if (std.mem.eql(u8, self.struct_array[i].name, struct_name)) break; if (i == self.struct_array.len) { return ZipponError.StructNotFound; } return self.struct_array[i].members; } pub fn structName2SchemaStruct(self: SchemaEngine, struct_name: []const u8) ZipponError!SchemaStruct { var i: usize = 0; while (i < self.struct_array.len) : (i += 1) if (std.mem.eql(u8, self.struct_array[i].name, struct_name)) break; if (i == self.struct_array.len) { return ZipponError.StructNotFound; } return self.struct_array[i]; } pub fn structName2DataType(self: SchemaEngine, struct_name: []const u8) ZipponError![]const DataType { var i: u16 = 0; while (i < self.struct_array.len) : (i += 1) { if (std.mem.eql(u8, self.struct_array[i].name, struct_name)) break; } if (i == self.struct_array.len and !std.mem.eql(u8, self.struct_array[i].name, struct_name)) { return ZipponError.StructNotFound; } return self.struct_array[i].types; } /// Chech if the name of a struct is in the current schema pub fn isStructNameExists(self: SchemaEngine, struct_name: []const u8) bool { var i: u16 = 0; while (i < self.struct_array.len) : (i += 1) if (std.mem.eql(u8, self.struct_array[i].name, struct_name)) return true; return false; } /// Check if a struct have the member name pub fn isMemberNameInStruct(self: SchemaEngine, struct_name: []const u8, member_name: []const u8) ZipponError!bool { for (try self.structName2structMembers(struct_name)) |mn| { if (std.mem.eql(u8, mn, member_name)) return true; } return false; } /// Return the SchemaStruct of the struct that the member is linked. So if it is not a link, it is itself, if it is a link, it the the sstruct of the link pub fn linkedStructName(self: SchemaEngine, struct_name: []const u8, member_name: []const u8) ZipponError!SchemaStruct { const sstruct = try self.structName2SchemaStruct(struct_name); if (sstruct.links.get(member_name)) |struct_link_name| { return try self.structName2SchemaStruct(struct_link_name); } return sstruct; } // Return true if the map have all the member name as key and not more pub fn checkIfAllMemberInMap( self: SchemaEngine, struct_name: []const u8, map: *std.StringHashMap(ConditionValue), error_message_buffer: *std.ArrayList(u8), ) ZipponError!bool { const all_struct_member = try self.structName2structMembers(struct_name); var count: u16 = 0; const writer = error_message_buffer.writer(); for (all_struct_member) |mn| { if (std.mem.eql(u8, mn, "id")) continue; if (map.contains(mn)) count += 1 else writer.print(" {s},", .{mn}) catch return ZipponError.WriteError; } return ((count == all_struct_member.len - 1) and (count == map.count())); } pub fn isUUIDExist(self: SchemaEngine, struct_name: []const u8, uuid: UUID) bool { const sstruct = self.structName2SchemaStruct(struct_name) catch return false; return sstruct.uuid_file_index.contains(uuid); } /// Create an array of empty RelationMap based on the additionalData pub fn relationMapArrayInit( self: SchemaEngine, alloc: Allocator, struct_name: []const u8, additional_data: AdditionalData, ) ZipponError![]RelationMap { // So here I should have relationship if children are relations var array = std.ArrayList(RelationMap).init(alloc); const sstruct = try self.structName2SchemaStruct(struct_name); for (additional_data.childrens.items) |child| if (sstruct.links.contains(child.name)) { const map = alloc.create(std.AutoHashMap([16]u8, JsonString)) catch return ZipponError.MemoryError; map.* = std.AutoHashMap([16]u8, JsonString).init(alloc); array.append(RelationMap{ .struct_name = sstruct.links.get(child.name).?, .member_name = child.name, .additional_data = child.additional_data, // Maybe I need to check if it exist, im not sure it always exist .map = map, }) catch return ZipponError.MemoryError; }; return array.toOwnedSlice() catch return ZipponError.MemoryError; } pub fn fileListToParse( self: SchemaEngine, alloc: Allocator, struct_name: []const u8, map: std.AutoHashMap([16]u8, JsonString), ) ZipponError![]usize { const sstruct = try self.structName2SchemaStruct(struct_name); var unique_indices = std.AutoHashMap(usize, void).init(alloc); defer unique_indices.deinit(); var iter = map.keyIterator(); while (iter.next()) |uuid| { if (sstruct.uuid_file_index.get(UUID{ .bytes = uuid.* })) |file_index| { unique_indices.put(file_index, {}) catch return ZipponError.MemoryError; } } var result = alloc.alloc(usize, unique_indices.count()) catch return ZipponError.MemoryError; var i: usize = 0; var index_iter = unique_indices.keyIterator(); while (index_iter.next()) |index| { result[i] = index.*; i += 1; } return result; }