diff --git a/README.md b/README.md index f012015..a283aa0 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Zippon have it's own query language. Here the keys point to remember: | GRAB User \| ASCENDING name \| | Get all users ordered by name | | GRAB User [name] { age > 10 AND name != 'Adrien' } \| DECENDING age \| | Get just the name of all users that are more than 10 years old and not named Adrien | | GRAB User [1] { bestfriend = { name = 'Adrien' } } | Get one user that has a best friend named Adrien | -| GRAB User [10; friends [1]] { age > 10 } | ASC name | | Get one friend of the 10th user above 10 years old in ascending name | +| GRAB User [10; friends [1]] { age > 10 } | Get one friend of the 10th user above 10 years old | ### Not yet implemented | Command | Description | diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..df9965d --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +[ ] Finish the filter parsing +[ ] Create a TEST_DATA dataset without any link +[ ] Do some test on filter using the TEST_DATA +[ ] Do some test on DataEngine to check if can add stuff to the TEST_DATA +[ ] Check memory leak of Parser +[ ] Do testing on parser diff --git a/src/ADD.zig b/src/ADD.zig index ab96c23..027e656 100644 --- a/src/ADD.zig +++ b/src/ADD.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const dtypes = @import("dtypes.zig"); +const metadata = @import("metadata.zig"); const UUID = @import("uuid.zig").UUID; const Tokenizer = @import("ziqlTokenizer.zig").Tokenizer; const Token = @import("ziqlTokenizer.zig").Token; @@ -25,144 +25,56 @@ const stdout = std.io.getStdOut().writer(); /// If no file is found, a new one is created. /// Take the main.zippondata file, the index of the file where the data is saved and the string to add at the end of the line pub const Parser = struct { - arena: std.heap.ArenaAllocator, allocator: Allocator, - toker: *Tokenizer, - data_engine: *DataEngine, - pub fn init(allocator: Allocator, toker: *Tokenizer, data_engine: *DataEngine) Parser { - var arena = std.heap.ArenaAllocator.init(allocator); + pub fn init(allocator: Allocator, toker: *Tokenizer) Parser { return Parser{ - .arena = arena, - .allocator = arena.allocator(), + .allocator = allocator, .toker = toker, - .data_engine = data_engine, }; } - pub fn deinit(self: *Parser) void { - self.arena.deinit(); - } + pub fn parse(self: *Parser) !void { + var data_engine = DataEngine.init(self.allocator, null); + defer data_engine.deinit(); + + var struct_name_token = self.toker.next(); + const struct_name = self.toker.getTokenSlice(struct_name_token); + + if (!metadata.isStructNameExists(struct_name)) self.print_error("Struct not found in current schema", &struct_name_token); - pub fn parse(self: *Parser, struct_name: []const u8) !void { var token = self.toker.next(); switch (token.tag) { .l_paren => {}, else => { - try self.print_error("Error: Expected (", &token); + self.print_error("Error: Expected (", &token); }, } - const buffer = try self.allocator.alloc(u8, 1024 * 100); - defer self.allocator.free(buffer); + var data_map = self.parseData(struct_name); + defer data_map.deinit(); - var data = self.parseData(); // data is a map with key as member name and value as str of the value inserted in the query. So age = 12 is the string 12 here - defer data.deinit(); - - if (!self.checkIfAllMemberInMap(struct_name, &data)) return; - - const entity = try dtypes.createEntityFromMap(self.allocator, struct_name, data); - const uuid_str = entity.User.*.id.format_uuid(); - defer stdout.print("Added new {s} successfully using UUID: {s}\n", .{ - struct_name, - uuid_str, - }) catch {}; - - const member_names = dtypes.structName2structMembers(struct_name); - for (member_names) |member_name| { - var file_map = self.data_engine.getFilesStat(struct_name, member_name) catch { - try stdout.print("Error: File stat error", .{}); - return; - }; - const potential_file_name_to_use = self.data_engine.getFirstUsableFile(file_map); - if (potential_file_name_to_use) |file_name| { - const file_index = self.data_engine.fileName2Index(file_name); - try stdout.print("Using file: {s} with a size of {d}\n", .{ file_name, file_map.get(file_name).?.size }); - - const path = try std.fmt.bufPrint(buffer, "ZipponDB/DATA/{s}/{s}/{s}", .{ - struct_name, - member_name, - file_name, - }); - - var file = std.fs.cwd().openFile(path, .{ - .mode = .read_write, - }) catch { - try stdout.print("Error opening data file.", .{}); - return; - }; - defer file.close(); - - try file.seekFromEnd(0); - try file.writer().print("{s} {s}\n", .{ uuid_str, data.get(member_name).? }); - - const path_to_main = try std.fmt.bufPrint(buffer, "ZipponDB/DATA/{s}/{s}/main.zippondata", .{ - struct_name, - member_name, - }); - - var file_main = std.fs.cwd().openFile(path_to_main, .{ - .mode = .read_write, - }) catch { - try stdout.print("Error opening data file.", .{}); - return; - }; - defer file_main.close(); - - try self.data_engine.appendToLineAtIndex(file_main, file_index, &uuid_str); - } else { - const max_index = self.data_engine.maxFileIndex(file_map); - - const new_file_path = try std.fmt.bufPrint(buffer, "ZipponDB/DATA/{s}/{s}/{d}.zippondata", .{ - struct_name, - member_name, - max_index + 1, - }); - - try stdout.print("new file path: {s}\n", .{new_file_path}); - - // TODO: Create new file and save the data inside - const new_file = std.fs.cwd().createFile(new_file_path, .{}) catch @panic("Error creating new data file"); - defer new_file.close(); - - try new_file.writer().print("{s} {s}\n", .{ &uuid_str, data.get(member_name).? }); - - const path_to_main = try std.fmt.bufPrint(buffer, "ZipponDB/DATA/{s}/{s}/main.zippondata", .{ - struct_name, - member_name, - }); - - var file_main = std.fs.cwd().openFile(path_to_main, .{ - .mode = .read_write, - }) catch { - try stdout.print("Error opening data file.", .{}); - @panic(""); - }; - defer file_main.close(); - - try file_main.seekFromEnd(0); - try file_main.writeAll("\n "); - try file_main.seekTo(0); - try self.data_engine.appendToLineAtIndex(file_main, max_index + 1, &uuid_str); - } - } + if (self.checkIfAllMemberInMap(struct_name, &data_map)) { + try data_engine.writeEntity(struct_name, data_map); + } else |_| {} } /// Take the tokenizer and return a map of the query for the ADD command. /// Keys are the member name and value are the string of the value in the query. E.g. 'Adrien' or '10' - pub fn parseData(self: *Parser) std.StringHashMap([]const u8) { + /// TODO: Make it clean using a State like other parser + pub fn parseData(self: *Parser, struct_name: []const u8) std.StringHashMap([]const u8) { var token = self.toker.next(); - var member_map = std.StringHashMap([]const u8).init( - self.allocator, - ); + var member_map = std.StringHashMap([]const u8).init(self.allocator); while (token.tag != Token.Tag.eof) : (token = self.toker.next()) { switch (token.tag) { .r_paren => continue, .identifier => { const member_name_str = self.toker.getTokenSlice(token); + + if (!metadata.isMemberNameInStruct(struct_name, member_name_str)) self.print_error("Member not found in struct.", &token); token = self.toker.next(); switch (token.tag) { .equal => { @@ -170,22 +82,23 @@ pub const Parser = struct { switch (token.tag) { .string_literal, .number_literal => { const value_str = self.toker.getTokenSlice(token); - member_map.put(member_name_str, value_str) catch @panic("Could not add member name and value to map in getMapOfMember"); + member_map.put(member_name_str, value_str) catch self.print_error("Could not add member name and value to map in getMapOfMember", &token); token = self.toker.next(); switch (token.tag) { .comma, .r_paren => continue, - else => self.print_error("Error: Expected , after string or number. E.g. ADD User (name='bob', age=10)", &token) catch {}, + else => self.print_error("Error: Expected , after string or number. E.g. ADD User (name='bob', age=10)", &token), } }, .keyword_null => { const value_str = "null"; - member_map.put(member_name_str, value_str) catch self.print_error("Error: 001", &token) catch {}; + member_map.put(member_name_str, value_str) catch self.print_error("Error: 001", &token); token = self.toker.next(); switch (token.tag) { .comma, .r_paren => continue, - else => self.print_error("Error: Expected , after string or number. E.g. ADD User (name='bob', age=10)", &token) catch {}, + else => self.print_error("Error: Expected , after string or number. E.g. ADD User (name='bob', age=10)", &token), } }, + // Create a tag to prevent creating an array then join them. Instead just read the buffer from [ to ] in the tekenizer itself .l_bracket => { var array_values = std.ArrayList([]const u8).init(self.allocator); token = self.toker.next(); @@ -193,60 +106,78 @@ pub const Parser = struct { switch (token.tag) { .string_literal, .number_literal => { const value_str = self.toker.getTokenSlice(token); - array_values.append(value_str) catch @panic("Could not add value to array in getMapOfMember"); + array_values.append(value_str) catch self.print_error("Could not add value to array in getMapOfMember", &token); }, - else => self.print_error("Error: Expected string or number in array. E.g. ADD User (scores=[10 20 30])", &token) catch {}, + else => self.print_error("Error: Expected string or number in array. E.g. ADD User (scores=[10 20 30])", &token), } } // Maybe change that as it just recreate a string that is already in the buffer - const array_str = std.mem.join(self.allocator, " ", array_values.items) catch @panic("Couln't join the value of array"); - member_map.put(member_name_str, array_str) catch @panic("Could not add member name and value to map in getMapOfMember"); + const array_str = std.mem.join(self.allocator, " ", array_values.items) catch { + self.print_error("Couln't join the value of array", &token); + @panic("=)"); + }; + member_map.put(member_name_str, array_str) catch self.print_error("Could not add member name and value to map in getMapOfMember", &token); + + token = self.toker.next(); + switch (token.tag) { + .comma, .r_paren => continue, + else => self.print_error("Error: Expected , after string or number. E.g. ADD User (name='bob', age=10)", &token), + } }, - else => self.print_error("Error: Expected string or number after =. E.g. ADD User (name='bob')", &token) catch {}, + else => self.print_error("Error: Expected string or number after =. E.g. ADD User (name='bob')", &token), } }, - else => self.print_error("Error: Expected = after a member declaration. E.g. ADD User (name='bob')", &token) catch {}, + else => self.print_error("Error: Expected = after a member declaration. E.g. ADD User (name='bob')", &token), } }, - else => self.print_error("Error: Unknow token. This should be the name of a member. E.g. name in ADD User (name='bob')", &token) catch {}, + else => self.print_error("Error: Unknow token. This should be the name of a member. E.g. name in ADD User (name='bob')", &token), } } return member_map; } - fn checkIfAllMemberInMap(_: *Parser, struct_name: []const u8, map: *std.StringHashMap([]const u8)) bool { - const all_struct_member = dtypes.structName2structMembers(struct_name); + const AddError = error{NotAllMemberInMap}; + + fn checkIfAllMemberInMap(_: *Parser, struct_name: []const u8, map: *std.StringHashMap([]const u8)) !void { + const all_struct_member = metadata.structName2structMembers(struct_name); var count: u16 = 0; + var started_printing = false; for (all_struct_member) |key| { - if (map.contains(key)) count += 1 else stdout.print("Error: ADD query of struct: {s}; missing member: {s}\n", .{ - struct_name, - key, - }) catch {}; + if (map.contains(key)) count += 1 else { + if (!started_printing) { + try stdout.print("Error: ADD query of struct: {s}; missing member: {s}", .{ struct_name, key }); + started_printing = true; + } else { + try stdout.print(" {s}", .{key}); + } + } } - return ((count == all_struct_member.len) and (count == map.count())); + if (started_printing) try stdout.print("\n", .{}); + + if (!((count == all_struct_member.len) and (count == map.count()))) return error.NotAllMemberInMap; } - fn print_error(self: *Parser, message: []const u8, token: *Token) !void { - try stdout.print("\n", .{}); - try stdout.print("{s}\n", .{self.toker.buffer}); + fn print_error(self: *Parser, message: []const u8, token: *Token) void { + stdout.print("\n", .{}) catch {}; + stdout.print("{s}\n", .{self.toker.buffer}) catch {}; // Calculate the number of spaces needed to reach the start position. var spaces: usize = 0; while (spaces < token.loc.start) : (spaces += 1) { - try stdout.print(" ", .{}); + stdout.print(" ", .{}) catch {}; } // Print the '^' characters for the error span. var i: usize = token.loc.start; while (i < token.loc.end) : (i += 1) { - try stdout.print("^", .{}); + stdout.print("^", .{}) catch {}; } - try stdout.print(" \n", .{}); // Align with the message + stdout.print(" \n", .{}) catch {}; // Align with the message - try stdout.print("{s}\n", .{message}); + stdout.print("{s}\n", .{message}) catch {}; @panic(""); } diff --git a/src/GRAB.zig b/src/GRAB.zig index 9add188..b5a62ac 100644 --- a/src/GRAB.zig +++ b/src/GRAB.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const metadata = @import("metadata.zig"); const Allocator = std.mem.Allocator; const Tokenizer = @import("ziqlTokenizer.zig").Tokenizer; const Token = @import("ziqlTokenizer.zig").Token; @@ -20,18 +21,16 @@ pub const Parser = struct { arena: std.heap.ArenaAllocator, allocator: Allocator, toker: *Tokenizer, - data_engine: *DataEngine, state: State, additional_data: AdditionalData, - pub fn init(allocator: Allocator, toker: *Tokenizer, data_engine: *DataEngine) Parser { + pub fn init(allocator: Allocator, toker: *Tokenizer) Parser { var arena = std.heap.ArenaAllocator.init(allocator); return Parser{ .arena = arena, .allocator = arena.allocator(), .toker = toker, - .data_engine = data_engine, .state = State.start, .additional_data = AdditionalData.init(allocator), }; @@ -52,8 +51,9 @@ pub const Parser = struct { } pub fn deinit(self: *AdditionalData) void { - for (self.member_to_find.items) |elem| { - elem.additional_data.deinit(); + for (0..self.member_to_find.items.len) |i| { + std.debug.print("{d}\n", .{i}); + self.member_to_find.items[i].additional_data.deinit(); } self.member_to_find.deinit(); @@ -77,32 +77,84 @@ pub const Parser = struct { invalid, end, - // For the additional data + // For the main parse function + expect_filter, + + // For the additional data parser expect_count_of_entity_to_find, expect_semicolon_OR_right_bracket, expect_member, next_member_OR_end_OR_new_additional_data, next_member_OR_end, - expect_filter, + // For the filter parser + expect_condition, }; pub fn parse(self: *Parser) !void { + var data_engine = DataEngine.init(self.allocator, null); + defer data_engine.deinit(); + + var struct_name_token = self.toker.next(); + if (!self.isStructInSchema(self.toker.getTokenSlice(struct_name_token))) { + try self.printError("Error: Struct name not in current shema.", &struct_name_token); + return; + } + var token = self.toker.next(); - while (self.state != State.end) : (token = self.toker.next()) { + var keep_next = false; + + while (self.state != State.end) : ({ + token = if (!keep_next) self.toker.next() else token; + keep_next = false; + }) { switch (self.state) { .start => { switch (token.tag) { .l_bracket => { - try self.parse_additional_data(&self.additional_data); + try self.parseAdditionalData(&self.additional_data); + self.state = State.expect_filter; + }, + .l_brace => { + self.state = State.expect_filter; + keep_next = true; }, else => { - try stdout.print("Found {any}\n", .{token.tag}); - + try self.printError("Error: Expected filter starting with {} or what to return starting with []", &token); return; }, } }, + .expect_filter => { + var array = std.ArrayList(UUID).init(self.allocator); + try self.parseFilter(&array, struct_name_token); + self.state = State.end; + }, + else => return, + } + } + } + + fn parseFilter(self: *Parser, left_array: *std.ArrayList(UUID), struct_name_token: Token) !void { + const right_array = std.ArrayList(UUID).init(self.allocator); + var token = self.toker.next(); + var keep_next = false; + self.state = State.expect_member; + + _ = right_array; + _ = left_array; + + while (self.state != State.end) : ({ + token = if (!keep_next) self.toker.next() else token; + keep_next = false; + }) { + switch (self.state) { + .expect_member => { + if (!self.isMemberPartOfStruct(self.toker.getTokenSlice(struct_name_token), self.toker.getTokenSlice(token))) { + try self.printError("Error: Member not part of struct.", &token); + } + self.state = State.expect_condition; + }, else => return, } } @@ -110,24 +162,21 @@ pub const Parser = struct { /// When this function is call, the tokenizer last token retrieved should be [. /// Check if an int is here -> check if ; is here -> check if member is here -> check if [ is here -> loop - pub fn parse_additional_data(self: *Parser, additional_data: *AdditionalData) !void { + pub fn parseAdditionalData(self: *Parser, additional_data: *AdditionalData) !void { var token = self.toker.next(); - var skip_next = false; + var keep_next = false; self.state = State.expect_count_of_entity_to_find; while (self.state != State.end) : ({ - token = if (!skip_next) self.toker.next() else token; - skip_next = false; + token = if (!keep_next) self.toker.next() else token; + keep_next = false; }) { switch (self.state) { .expect_count_of_entity_to_find => { switch (token.tag) { .number_literal => { const count = std.fmt.parseInt(usize, self.toker.getTokenSlice(token), 10) catch { - try stdout.print( - "Error parsing query: {s} need to be a number.", - .{self.toker.getTokenSlice(token)}, - ); + try self.printError("Error while transforming this into a integer.", &token); self.state = .invalid; continue; }; @@ -136,7 +185,7 @@ pub const Parser = struct { }, else => { self.state = .expect_member; - skip_next = true; + keep_next = true; }, } }, @@ -149,7 +198,7 @@ pub const Parser = struct { return; }, else => { - try self.print_error( + try self.printError( "Error: Expect ';' or ']'.", &token, ); @@ -171,7 +220,7 @@ pub const Parser = struct { self.state = .next_member_OR_end_OR_new_additional_data; }, else => { - try self.print_error( + try self.printError( "Error: A member name should be here.", &token, ); @@ -187,13 +236,13 @@ pub const Parser = struct { return; }, .l_bracket => { - try self.parse_additional_data( + try self.parseAdditionalData( &additional_data.member_to_find.items[additional_data.member_to_find.items.len - 1].additional_data, ); self.state = .next_member_OR_end; }, else => { - try self.print_error( + try self.printError( "Error: Expected a comma ',' or the end or a new list of member to return.", &token, ); @@ -203,15 +252,14 @@ pub const Parser = struct { .next_member_OR_end => { switch (token.tag) { .comma => { - try stdout.print("Expected new member\n", .{}); self.state = .expect_member; }, .r_bracket => { return; }, else => { - try self.print_error( - "Error: Expected a new member name or the end of the list of member name to return.", + try self.printError( + "Error: Expected a comma or the end of the list of member name to return.", &token, ); }, @@ -221,7 +269,7 @@ pub const Parser = struct { @panic("=)"); }, else => { - try self.print_error( + try self.printError( "Error: Unknow state.", &token, ); @@ -230,7 +278,7 @@ pub const Parser = struct { } } - fn print_error(self: *Parser, message: []const u8, token: *Token) !void { + fn printError(self: *Parser, message: []const u8, token: *Token) !void { try stdout.print("\n", .{}); try stdout.print("{s}\n", .{self.toker.buffer}); @@ -251,6 +299,27 @@ pub const Parser = struct { @panic(""); } + + /// Take a struct name and a member name and return true if the member name is part of the struct + fn isMemberPartOfStruct(_: *Parser, struct_name: []const u8, member_name: []const u8) bool { + const all_struct_member = metadata.structName2structMembers(struct_name); + + for (all_struct_member) |key| { + if (std.mem.eql(u8, key, member_name)) return true; + } + + return false; + } + + /// Check if a string is a name of a struct in the currently use engine + fn isStructInSchema(_: *Parser, struct_name_to_check: []const u8) bool { + for (metadata.struct_name_list) |struct_name| { + if (std.mem.eql(u8, struct_name_to_check, struct_name)) { + return true; + } + } + return false; + } }; // TODO: Optimize. Maybe just do a new list and return it instead @@ -292,7 +361,7 @@ test "Test AdditionalData" { testAdditionalData("[1]", additional_data1); var additional_data2 = Parser.AdditionalData.init(allocator); - defer additional_data2.member_to_find.deinit(); + defer additional_data2.deinit(); try additional_data2.member_to_find.append( Parser.AdditionalDataMember.init( allocator, @@ -303,7 +372,7 @@ test "Test AdditionalData" { var additional_data3 = Parser.AdditionalData.init(allocator); additional_data3.entity_count_to_find = 1; - defer additional_data3.member_to_find.deinit(); + defer additional_data3.deinit(); try additional_data3.member_to_find.append( Parser.AdditionalDataMember.init( allocator, @@ -314,7 +383,7 @@ test "Test AdditionalData" { var additional_data4 = Parser.AdditionalData.init(allocator); additional_data4.entity_count_to_find = 100; - defer additional_data4.member_to_find.deinit(); + defer additional_data4.deinit(); try additional_data4.member_to_find.append( Parser.AdditionalDataMember.init( allocator, diff --git a/src/data-parsing.zig b/src/data-parsing.zig index 8357bf3..389caae 100644 --- a/src/data-parsing.zig +++ b/src/data-parsing.zig @@ -19,6 +19,48 @@ pub fn parseArrayInt(allocator: std.mem.Allocator, array_str: []const u8) std.Ar return array; } +pub fn parseFloat(value_str: []const u8) f64 { + return std.fmt.parseFloat(f64, value_str) catch return 0; +} + +pub fn parseArrayFloat(allocator: std.mem.Allocator, array_str: []const u8) std.ArrayList(f64) { + var array = std.ArrayList(f64).init(allocator); + + var it = std.mem.splitAny(u8, array_str[1 .. array_str.len - 1], " "); + while (it.next()) |x| { + array.append(parseFloat(x)) catch {}; + } + + return array; +} + +pub fn parseBool(value_str: []const u8) bool { + return (value_str[0] != '0'); +} + +pub fn parseArrayBool(allocator: std.mem.Allocator, array_str: []const u8) std.ArrayList(bool) { + var array = std.ArrayList(bool).init(allocator); + + var it = std.mem.splitAny(u8, array_str[1 .. array_str.len - 1], " "); + while (it.next()) |x| { + array.append(parseBool(x)) catch {}; + } + + return array; +} + +pub fn parseArrayStr(allocator: std.mem.Allocator, array_str: []const u8) std.ArrayList([]const u8) { + var array = std.ArrayList([]const u8).init(allocator); + + var it = std.mem.splitAny(u8, array_str[1 .. array_str.len - 1], " "); + while (it.next()) |x| { + const x_copy = allocator.dupe(u8, x) catch @panic("=("); + array.append(x_copy) catch {}; + } + + return array; +} + test "Data parsing" { const allocator = std.testing.allocator; @@ -35,4 +77,32 @@ test "Data parsing" { defer out2.deinit(); const expected_out2: [5]i64 = .{ 1, 14, 44, 42, 0 }; try std.testing.expect(std.mem.eql(i64, out2.items, &expected_out2)); + + // Float + const in3: [3][]const u8 = .{ "1.3", "65.991", "Hello" }; + const expected_out3: [3]f64 = .{ 1.3, 65.991, 0 }; + for (in3, 0..) |value, i| { + try std.testing.expect(parseFloat(value) == expected_out3[i]); + } + + // Int array + const in4 = "[1.5 14.3 44.9999 42 hello]"; + const out4 = parseArrayFloat(allocator, in4); + defer out4.deinit(); + const expected_out4: [5]f64 = .{ 1.5, 14.3, 44.9999, 42, 0 }; + try std.testing.expect(std.mem.eql(f64, out4.items, &expected_out4)); + + // Float + const in5: [3][]const u8 = .{ "1", "Hello", "0" }; + const expected_out5: [3]bool = .{ true, true, false }; + for (in5, 0..) |value, i| { + try std.testing.expect(parseBool(value) == expected_out5[i]); + } + + // Int array + const in6 = "[1 0 0 1 1]"; + const out6 = parseArrayBool(allocator, in6); + defer out6.deinit(); + const expected_out6: [5]bool = .{ true, false, false, true, true }; + try std.testing.expect(std.mem.eql(bool, out6.items, &expected_out6)); } diff --git a/src/dataEngine.zig b/src/dataEngine.zig index 8dd3c64..ae0b960 100644 --- a/src/dataEngine.zig +++ b/src/dataEngine.zig @@ -1,42 +1,286 @@ const std = @import("std"); +const dataParsing = @import("data-parsing.zig"); +const metadata = @import("metadata.zig"); const Allocator = std.mem.Allocator; -const Tokenizer = @import("ziqlTokenizer.zig").Tokenizer; +const UUID = @import("uuid.zig").UUID; +const stdout = std.io.getStdOut().writer(); /// Manage everything that is relate to read or write in files /// Or even get stats, whatever. If it touch files, it's here pub const DataEngine = struct { - arena: std.heap.ArenaAllocator, allocator: Allocator, dir: std.fs.Dir, // The path to the DATA folder max_file_size: usize = 1e+8, // 100mb + // + const DataEngineError = error{ + ErrorCreateDataFolder, + ErrorCreateStructFolder, + ErrorCreateMemberFolder, + ErrorCreateMainFile, + ErrorCreateDataFile, + }; - pub fn init(allocator: Allocator) DataEngine { - var arena = std.heap.ArenaAllocator.init(allocator); - const dir = std.fs.cwd().openDir("ZipponDB/DATA", .{}) catch @panic("Error opening ZipponDB/DATA"); + /// Suported operation for the filter + /// TODO: Add more operation, like IN for array and LIKE for regex + const Operation = enum { + equal, + different, + superior, + superior_or_equal, + inferior, + inferior_or_equal, + }; + + /// Suported dataType for the DB + const DataType = enum { + int, + float, + str, + bool_, + int_array, + float_array, + str_array, + bool_array, + }; + + const ComparisonValue = union { + int: i64, + float: f64, + str: []const u8, + bool_: bool, + int_array: std.ArrayList(i64), + str_array: std.ArrayList([]const u8), + float_array: std.ArrayList(f64), + bool_array: std.ArrayList(bool), + }; + + /// use to parse file. It take a struct name and member name to know what to parse. + /// An Operation from equal, different, superior, superior_or_equal, ... + /// The DataType from int, float and str + /// TODO: Change the value to be the right type and not just a string all the time + const Condition = struct { + struct_name: []const u8, + member_name: []const u8, + value: []const u8, + operation: Operation, + data_type: DataType, + }; + + pub fn init(allocator: Allocator, DATA_path: ?[]const u8) DataEngine { + const path = DATA_path orelse "ZipponDB/DATA"; + const dir = std.fs.cwd().openDir(path, .{}) catch @panic("Error opening ZipponDB/DATA"); return DataEngine{ - .arena = arena, - .allocator = arena.allocator(), + .allocator = allocator, .dir = dir, }; } pub fn deinit(self: *DataEngine) void { - self.arena.deinit(); + self.dir.close(); } - /// Iter over all file and get the max name and return the value of it as usize - /// So for example if there is 1.zippondata and 2.zippondata it return 2. - fn maxFileIndex(_: *DataEngine, map: *std.StringHashMap(std.fs.File.Stat)) usize { - var iter = map.keyIterator(); - var index_max: usize = 0; - while (iter.next()) |key| { - if (std.mem.eql(u8, key.*, "main.zippondata")) continue; - var iter_file_name = std.mem.tokenize(u8, key.*, "."); - const num_str = iter_file_name.next().?; - const num: usize = std.fmt.parseInt(usize, num_str, 10) catch @panic("Error parsing file name into usize"); - if (num > index_max) index_max = num; + /// Take a condition and an array of UUID and fill the array with all UUID that match the condition + pub fn getUUIDListUsingCondition(self: *DataEngine, condition: Condition, uuid_array: *std.ArrayList(UUID)) !void { + const file_names = self.getFilesNames(condition.struct_name, condition.member_name) catch @panic("Can't get list of files"); + defer self.deinitFilesNames(&file_names); + + const sub_path = std.fmt.allocPrint( + self.allocator, + "{s}/{s}/{s}", + .{ condition.struct_name, condition.member_name, file_names.items[0] }, + ) catch @panic("Can't create sub_path for init a DataIterator"); + defer self.allocator.free(sub_path); + + var file = self.dir.openFile(sub_path, .{}) catch @panic("Can't open first file to init a data iterator"); + // defer self.allocator.free(sub_path); + + var output: [1024 * 50]u8 = undefined; // Maybe need to increase that as it limit the size of a line in files + var output_fbs = std.io.fixedBufferStream(&output); + const writer = output_fbs.writer(); + + var buffered = std.io.bufferedReader(file.reader()); + var reader = buffered.reader(); + + var file_index: usize = 0; + + var compare_value: ComparisonValue = undefined; + switch (condition.data_type) { + .int => compare_value = ComparisonValue{ .int = dataParsing.parseInt(condition.value) }, + .str => compare_value = ComparisonValue{ .str = condition.value }, + .float => compare_value = ComparisonValue{ .float = dataParsing.parseFloat(condition.value) }, + .bool_ => compare_value = ComparisonValue{ .bool_ = dataParsing.parseBool(condition.value) }, + .int_array => compare_value = ComparisonValue{ .int_array = dataParsing.parseArrayInt(self.allocator, condition.value) }, + .str_array => compare_value = ComparisonValue{ .str_array = dataParsing.parseArrayStr(self.allocator, condition.value) }, + .float_array => compare_value = ComparisonValue{ .float_array = dataParsing.parseArrayFloat(self.allocator, condition.value) }, + .bool_array => compare_value = ComparisonValue{ .bool_array = dataParsing.parseArrayBool(self.allocator, condition.value) }, + } + + while (true) { + output_fbs.reset(); + reader.streamUntilDelimiter(writer, '\n', null) catch |err| switch (err) { + error.EndOfStream => { + output_fbs.reset(); // clear buffer before exit + file_index += 1; + + if (file_index == file_names.items.len) break; + + // TODO: Update the file and reader to be the next file of the list + + break; + }, // file read till the end + else => { + std.debug.print("Error while reading file: {any}\n", .{err}); + break; + }, + }; + + // TODO: Maybe put that directly inside the union type like a compare function + // Can also do the switch directly on the compare_value + // TODO: Add error for wrong condition like superior between 2 string or array + switch (condition.operation) { + .equal => { + switch (condition.data_type) { + .int => if (compare_value.int == dataParsing.parseInt(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .float => if (compare_value.float == dataParsing.parseFloat(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .str => if (std.mem.eql(u8, compare_value.str, output_fbs.getWritten()[38 .. output_fbs.getWritten().len - 1])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .bool_ => if (compare_value.bool_ == dataParsing.parseBool(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + // TODO: Implement for array too + else => {}, + } + }, + .different => { + switch (condition.data_type) { + .int => if (compare_value.int != dataParsing.parseInt(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .float => if (compare_value.float != dataParsing.parseFloat(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .str => if (!std.mem.eql(u8, compare_value.str, output_fbs.getWritten()[38 .. output_fbs.getWritten().len - 1])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .bool_ => if (compare_value.bool_ != dataParsing.parseBool(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + // TODO: Implement for array too + else => {}, + } + }, + .superior_or_equal => { + switch (condition.data_type) { + .int => if (compare_value.int <= dataParsing.parseInt(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .float => if (compare_value.float <= dataParsing.parseFloat(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + // TODO: Implement for array too + else => {}, + } + }, + .superior => { + switch (condition.data_type) { + .int => if (compare_value.int < dataParsing.parseInt(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .float => if (compare_value.float < dataParsing.parseFloat(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + // TODO: Implement for array too + else => {}, + } + }, + .inferior_or_equal => { + switch (condition.data_type) { + .int => if (compare_value.int >= dataParsing.parseInt(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .float => if (compare_value.float >= dataParsing.parseFloat(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + // TODO: Implement for array too + else => {}, + } + }, + .inferior => { + switch (condition.data_type) { + .int => if (compare_value.int > dataParsing.parseInt(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + .float => if (compare_value.float > dataParsing.parseFloat(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), + // TODO: Implement for array too + else => {}, + } + }, + } + } + } + + // TODO: Test leak on that + pub fn writeEntity(self: *DataEngine, struct_name: []const u8, data_map: std.StringHashMap([]const u8)) !void { + const uuid_str = UUID.init().format_uuid(); + defer stdout.print("Added new {s} successfully using UUID: {s}\n", .{ + struct_name, + uuid_str, + }) catch {}; + + const member_names = metadata.structName2structMembers(struct_name); + for (member_names) |member_name| { + const potential_file_name_to_use = try self.getFirstUsableFile(struct_name, member_name); + + if (potential_file_name_to_use) |file_name| { + defer self.allocator.free(file_name); + + const file_index = self.fileName2Index(file_name); + + const path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}", .{ + struct_name, + member_name, + file_name, + }); + defer self.allocator.free(path); + + var file = self.dir.openFile(path, .{ + .mode = .read_write, + }) catch { + try stdout.print("Error opening data file.", .{}); + return; + }; + defer file.close(); + + try file.seekFromEnd(0); + try file.writer().print("{s} {s}\n", .{ uuid_str, data_map.get(member_name).? }); + + const path_to_main = try std.fmt.allocPrint(self.allocator, "{s}/{s}/main.zippondata", .{ + struct_name, + member_name, + }); + defer self.allocator.free(path_to_main); + + var file_main = self.dir.openFile(path_to_main, .{ + .mode = .read_write, + }) catch { + try stdout.print("Error opening data file.", .{}); + return; + }; + defer file_main.close(); + + try self.addUUIDToMainFile(file_main, file_index + 1, &uuid_str); + } else { + const max_index = try self.maxFileIndex(struct_name, member_name); + + const new_file_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{d}.zippondata", .{ + struct_name, + member_name, + max_index + 1, + }); + + try stdout.print("new file path: {s}\n", .{new_file_path}); + + // TODO: Create new file and save the data inside + const new_file = self.dir.createFile(new_file_path, .{}) catch @panic("Error creating new data file"); + defer new_file.close(); + + try new_file.writer().print("{s} {s}\n", .{ &uuid_str, data_map.get(member_name).? }); + + const path_to_main = try std.fmt.allocPrint(self.allocator, "ZipponDB/DATA/{s}/{s}/main.zippondata", .{ + struct_name, + member_name, + }); + defer self.allocator.free(path_to_main); + + var file_main = self.dir.openFile(path_to_main, .{ + .mode = .read_write, + }) catch { + try stdout.print("Error opening data file.", .{}); + @panic(""); + }; + defer file_main.close(); + + try file_main.seekFromEnd(0); + try file_main.writeAll("\n "); + try file_main.seekTo(0); + try self.addUUIDToMainFile(file_main, max_index + 1, &uuid_str); + } } - return index_max; } /// Use a filename in the format 1.zippondata and return the 1 @@ -49,55 +293,135 @@ pub const DataEngine = struct { /// Add an UUID at a specific index of a file /// Used when some data are deleted from previous zippondata files and are now bellow the file size limit - fn appendToLineAtIndex(self: *DataEngine, file: std.fs.File, index: usize, str: []const u8) !void { - const buffer = try self.allocator.alloc(u8, 1024 * 100); - defer self.allocator.free(buffer); + fn addUUIDToMainFile(_: *DataEngine, file: std.fs.File, index: usize, uuid_str: []const u8) !void { + var output: [1024 * 50]u8 = undefined; // Maybe need to increase that as it limit the size of a line in files + var output_fbs = std.io.fixedBufferStream(&output); + const writer = output_fbs.writer(); var reader = file.reader(); var line_num: usize = 1; - while (try reader.readUntilDelimiterOrEof(buffer, '\n')) |_| { + while (true) { + output_fbs.reset(); + reader.streamUntilDelimiter(writer, '\n', null) catch |err| switch (err) { // Maybe do a better error handeling. Because if an error happend here, data are already written in files but not in main + error.EndOfStream => { + output_fbs.reset(); // clear buffer before exit + break; + }, // file read till the end + else => break, + }; + if (line_num == index) { try file.seekBy(-1); - try file.writer().print("{s} ", .{str}); + try file.writer().print("{s} ", .{uuid_str}); return; } line_num += 1; } } - /// Return a map of file path => Stat; for one struct and member name - /// E.g. for User & name - fn getFilesStat(self: *DataEngine, struct_name: []const u8, member_name: []const u8) !*std.StringHashMap(std.fs.File.Stat) { - const buffer = try self.allocator.alloc(u8, 1024); // Adjust the size as needed - defer self.allocator.free(buffer); + fn getFilesNames(self: *DataEngine, struct_name: []const u8, member_name: []const u8) !std.ArrayList([]const u8) { + const sub_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ struct_name, member_name }); - const path = try std.fmt.bufPrint(buffer, "{s}{s}/{s}", .{ self.path.basename(), struct_name, member_name }); + var file_names = std.ArrayList([]const u8).init(self.allocator); - var file_map = std.StringHashMap(std.fs.File.Stat).init(self.allocator); - - const member_dir = self.path.openDir(path, .{ .iterate = true }) catch @panic("Error opening struct directory"); + const member_dir = self.dir.openDir(sub_path, .{ .iterate = true }) catch @panic("Error opening member directory"); + defer self.allocator.free(sub_path); var iter = member_dir.iterate(); while (try iter.next()) |entry| { - if (entry.kind != std.fs.Dir.Entry.Kind.file) continue; - - const file_stat = member_dir.statFile(entry.name) catch @panic("Error getting stat of a file"); - - file_map.put(entry.name, file_stat) catch @panic("Error adding stat to map"); + if ((entry.kind != std.fs.Dir.Entry.Kind.file) or (std.mem.eql(u8, "main.zippondata", entry.name))) continue; + try file_names.append(try self.allocator.dupe(u8, entry.name)); } - return &file_map; + return file_names; + } + + fn deinitFilesNames(self: *DataEngine, array: *const std.ArrayList([]const u8)) void { + for (array.items) |elem| { + self.allocator.free(elem); + } + array.deinit(); } /// Use the map of file stat to find the first file with under the bytes limit. /// return the name of the file. If none is found, return null. - fn getFirstUsableFile(self: *DataEngine, map: *std.StringHashMap(std.fs.File.Stat)) ?[]const u8 { - var iter = map.keyIterator(); - while (iter.next()) |key| { - if (std.mem.eql(u8, key.*, "main.zippondata")) continue; - if (map.get(key.*).?.size < self.max_file_size) return key.*; + fn getFirstUsableFile(self: *DataEngine, struct_name: []const u8, member_name: []const u8) !?[]const u8 { + const sub_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ struct_name, member_name }); + defer self.allocator.free(sub_path); + var member_dir = try self.dir.openDir(sub_path, .{ .iterate = true }); + defer member_dir.close(); + + var iter = member_dir.iterate(); + while (try iter.next()) |entry| { + if ((entry.kind != std.fs.Dir.Entry.Kind.file) or (std.mem.eql(u8, "main.zippondata", entry.name))) continue; + + const file_stat = try member_dir.statFile(entry.name); + if (file_stat.size < self.max_file_size) return try self.allocator.dupe(u8, entry.name); } return null; } + + /// Iter over all file and get the max name and return the value of it as usize + /// So for example if there is 1.zippondata and 2.zippondata it return 2. + fn maxFileIndex(self: *DataEngine, struct_name: []const u8, member_name: []const u8) !usize { + const buffer = try self.allocator.alloc(u8, 1024); // Adjust the size as needed + defer self.allocator.free(buffer); + + const sub_path = try std.fmt.bufPrint(buffer, "{s}/{s}", .{ struct_name, member_name }); + const member_dir = try self.dir.openDir(sub_path, .{ .iterate = true }); + var count: usize = 0; + + var iter = member_dir.iterate(); + while (try iter.next()) |entry| { + if ((entry.kind != std.fs.Dir.Entry.Kind.file) or (std.mem.eql(u8, "main.zippondata", entry.name))) continue; + count += 1; + } + return count; + } + + // TODO: Give the option to keep , dump or erase the data + pub fn initDataFolder(self: *DataEngine) !void { + for (metadata.struct_name_list) |struct_name| { + self.dir.makeDir(struct_name) catch |err| switch (err) { + error.PathAlreadyExists => {}, + else => return DataEngineError.ErrorCreateStructFolder, + }; + const struct_dir = try self.dir.openDir(struct_name, .{}); + + const member_names = metadata.structName2structMembers(struct_name); + for (member_names) |member_name| { + struct_dir.makeDir(member_name) catch |err| switch (err) { + error.PathAlreadyExists => continue, + else => return DataEngineError.ErrorCreateMemberFolder, + }; + const member_dir = try struct_dir.openDir(member_name, .{}); + + blk: { + const file = member_dir.createFile("main.zippondata", .{}) catch |err| switch (err) { + error.PathAlreadyExists => break :blk, + else => return DataEngineError.ErrorCreateMainFile, + }; + try file.writeAll("\n"); + } + _ = member_dir.createFile("0.zippondata", .{}) catch |err| switch (err) { + error.PathAlreadyExists => {}, + else => return DataEngineError.ErrorCreateDataFile, + }; + } + } + } }; + +test "File iterator" { + const allocator = std.testing.allocator; + var data_engine = DataEngine.init(allocator, null); + + var uuid_array = std.ArrayList(UUID).init(allocator); + defer uuid_array.deinit(); + + const condition = DataEngine.Condition{ .struct_name = "User", .member_name = "email", .value = "adrien@mail.com", .operation = .equal, .data_type = .str }; + try data_engine.getUUIDListUsingCondition(condition, &uuid_array); + + std.debug.print("Found {d} uuid with first as {any}\n\n", .{ uuid_array.items.len, uuid_array.items[0] }); +} diff --git a/src/dbconsole.zig b/src/dbconsole.zig index dde6342..b2cd8c4 100644 --- a/src/dbconsole.zig +++ b/src/dbconsole.zig @@ -152,37 +152,7 @@ fn buildEngine() !void { try child.spawn(); _ = try child.wait(); - const dtypes = @import("dtypes.zig"); - - const data_dir = try std.fs.cwd().openDir("ZipponDB/DATA", .{}); - for (dtypes.struct_name_list) |struct_name| { - data_dir.makeDir(struct_name) catch |err| switch (err) { - error.PathAlreadyExists => {}, - else => @panic("Error other than path already exists when trying to create a struct directory.\n"), - }; - const struct_dir = try data_dir.openDir(struct_name, .{}); - - const member_names = dtypes.structName2structMembers(struct_name); - for (member_names) |member_name| { - struct_dir.makeDir(member_name) catch |err| switch (err) { - error.PathAlreadyExists => return, - else => @panic("Error other than path already exists when trying to create a member directory.\n"), - }; - const member_dir = try struct_dir.openDir(member_name, .{}); - - blk: { - const file = member_dir.createFile("main.zippondata", .{}) catch |err| switch (err) { - error.PathAlreadyExists => break :blk, - else => @panic("Error: can't create main.zippondata"), - }; - try file.writeAll("\n"); - } - _ = member_dir.createFile("1.zippondata", .{}) catch |err| switch (err) { - error.PathAlreadyExists => {}, - else => @panic("Error: can't create 1.zippondata"), - }; - } - } + runCommand("__INIT__"); } fn runCommand(null_term_query_str: [:0]const u8) void { @@ -192,7 +162,7 @@ fn runCommand(null_term_query_str: [:0]const u8) void { // TODO: Use the folder ENGINE const args = &[_][]const u8{ "./engine", null_term_query_str }; - const result = std.process.Child.run(.{ .allocator = allocator, .argv = args, .max_output_bytes = 4084 }) catch |err| switch (err) { + const result = std.process.Child.run(.{ .allocator = allocator, .argv = args }) catch |err| switch (err) { error.FileNotFound => { std.debug.print("No engine found, please use `schema build` to make one.\n", .{}); return; diff --git a/src/dbengine.zig b/src/dbengine.zig index 2784b0d..aa07c9b 100644 --- a/src/dbengine.zig +++ b/src/dbengine.zig @@ -1,12 +1,8 @@ const std = @import("std"); -const dtypes = @import("dtypes.zig"); -const UUID = @import("uuid.zig").UUID; +const DataEngine = @import("dataEngine.zig").DataEngine; const Tokenizer = @import("ziqlTokenizer.zig").Tokenizer; -const Token = @import("ziqlTokenizer.zig").Token; const grabParser = @import("GRAB.zig").Parser; const addParser = @import("ADD.zig").Parser; -const DataEngine = @import("dataEngine.zig").DataEngine; -const Allocator = std.mem.Allocator; pub const Error = error{UUIDNotFound}; const stdout = std.io.getStdOut().writer(); @@ -27,26 +23,17 @@ pub fn main() !void { var toker = Tokenizer.init(null_term_query_str); const first_token = toker.next(); - const struct_name_token = toker.next(); - - var data_engine = DataEngine.init(allocator); switch (first_token.tag) { .keyword_grab => { - if (!isStructInSchema(toker.getTokenSlice(struct_name_token))) { - try stdout.print("Error: No struct named '{s}' in current schema.", .{toker.getTokenSlice(struct_name_token)}); - return; - } - var parser = grabParser.init(allocator, &toker, &data_engine); + var parser = grabParser.init(allocator, &toker); try parser.parse(); }, .keyword_add => { - if (!isStructInSchema(toker.getTokenSlice(struct_name_token))) { - try stdout.print("Error: No struct named '{s}' in current schema.", .{toker.getTokenSlice(struct_name_token)}); - return; - } - var parser = addParser.init(allocator, &toker, &data_engine); - try parser.parse(toker.getTokenSlice(struct_name_token)); + var parser = addParser.init(allocator, &toker); + parser.parse() catch |err| { + try stdout.print("Error: {any} while parsin ADD.\n", .{err}); + }; }, .keyword_update => { try stdout.print("Not yet implemented.\n", .{}); @@ -55,22 +42,14 @@ pub fn main() !void { try stdout.print("Not yet implemented.\n", .{}); }, .keyword__describe__ => { - try stdout.print("{s}", .{dtypes.describe_str}); + try stdout.print("{s}", .{@embedFile("schema.zipponschema")}); + }, + .keyword__init__ => { + var data_engine = DataEngine.init(allocator, null); + try data_engine.initDataFolder(); }, else => { try stdout.print("Query need to start with a keyword, including: GRAB ADD UPDATE DELETE\n", .{}); }, } } - -/// Check if a string is a name of a struct in the currently use engine -fn isStructInSchema(struct_name_to_check: []const u8) bool { - if (std.mem.eql(u8, struct_name_to_check, "describe")) return true; - - for (dtypes.struct_name_list) |struct_name| { - if (std.mem.eql(u8, struct_name_to_check, struct_name)) { - return true; - } - } - return false; -} diff --git a/src/dtypes.zig b/src/dtypes.zig deleted file mode 100644 index 0b47a7e..0000000 --- a/src/dtypes.zig +++ /dev/null @@ -1,70 +0,0 @@ -const std = @import("std"); -const UUID = @import("uuid.zig").UUID; -const dataParsing = @import("data-parsing.zig"); - -pub const User = struct { - id: UUID, - name: []const u8, - email: []const u8, - age: i64, - scores: []i64, - - pub fn init(name: []const u8, email: []const u8, age: i64, scores: []i64) User { - return User{ .id = UUID.init(), .name = name, .email = email, .age = age, .scores = scores }; - } -}; - -pub const Message = struct { - id: UUID, - content: []const u8, - - pub fn init(content: []const u8) Message { - return Message{ .id = UUID.init(), .content = content }; - } -}; - -pub const Types = union { - User: *const User, - Message: *const Message, -}; - -// Maybe create a struct like StructMetadata for the string list of member and name, ect -pub const struct_name_list: [2][]const u8 = .{ - "User", - "Message", -}; - -pub const struct_member_list: [2][]const []const u8 = .{ - &[_][]const u8{ "name", "email", "age", "scores" }, - &[_][]const u8{"content"}, -}; - -// For now there is 4 types of data: str, int, float, bool -const MemberTypes = enum { int, float, bool, str }; - -pub const describe_str = "User (\n name: str,\n email: str,\n)\n\nMessage (\n content: str,\n)\n"; - -/// User a map of member name / value string to create a new entity of a type -/// The goal being an array of map while parsing files to then return an array of entities and do some fileting on it. -pub fn createEntityFromMap(allocator: std.mem.Allocator, struct_name: []const u8, map: std.StringHashMap([]const u8)) !*Types { - var t = try allocator.create(Types); - if (std.mem.eql(u8, struct_name, "User")) { - const age = try std.fmt.parseInt(i64, map.get("age").?, 10); - const scores = dataParsing.parseArrayInt(allocator, map.get("scores").?); - t.User = &User.init(map.get("name").?, map.get("email").?, age, scores.items); - } else { - return error.UnknowStructName; - } - return t; -} - -/// Get the list of all member name for a struct name -pub fn structName2structMembers(struct_name: []const u8) []const []const u8 { - var i: u16 = 0; - - while (i < struct_name_list.len) : (i += 1) { - if (std.mem.eql(u8, struct_name_list[i], struct_name)) break; - } - - return struct_member_list[i]; -} diff --git a/src/dtypes_test.zig b/src/dtypes_test.zig deleted file mode 100644 index 62f8060..0000000 --- a/src/dtypes_test.zig +++ /dev/null @@ -1,62 +0,0 @@ -const std = @import("std"); -const UUID = @import("uuid.zig").UUID; - -pub const parameter_max_file_size_in_bytes = 500; // THe number of bytes than each file can be before splitting - -pub const User = struct { - id: UUID, - name: []const u8, - email: []const u8, - - pub fn init(name: []const u8, email: []const u8) User { - return User{ .id = UUID.init(), .name = name, .email = email }; - } -}; - -pub const Message = struct { - id: UUID, - content: []const u8, - - pub fn init(content: []const u8) Message { - return Message{ .id = UUID.init(), .content = content }; - } -}; - -pub const Types = union { - User: *const User, - Message: *const Message, -}; - -// Maybe create a struct like StructMetadata for the string list of member and name, ect -pub const struct_name_list: [2][]const u8 = .{ - "User", - "Message", -}; - -pub const struct_member_list: [2][]const []const u8 = .{ - &[_][]const u8{ "name", "email" }, - &[_][]const u8{"content"}, -}; - -pub const describe_str = "User (\n name: str,\n email: str,\n)\n\nMessage (\n content: str,\n)\n"; - -pub fn createEntityFromMap(allocator: std.mem.Allocator, struct_name: []const u8, map: std.StringHashMap([]const u8)) !*Types { - var t = try allocator.create(Types); - if (std.mem.eql(u8, struct_name, "User")) { - t.User = &User.init(map.get("name").?, map.get("email").?); - } else { - return error.UnknowStructName; - } - return t; -} - -/// Get the list of all member name for a struct name -pub fn structName2structMembers(struct_name: []const u8) []const []const u8 { - var i: u16 = 0; - - while (i < struct_name_list.len) : (i += 1) { - if (std.mem.eql(u8, struct_name_list[i], struct_name)) break; - } - - return struct_member_list[i]; -} diff --git a/src/metadata.zig b/src/metadata.zig new file mode 100644 index 0000000..e9b01b4 --- /dev/null +++ b/src/metadata.zig @@ -0,0 +1,31 @@ +const std = @import("std"); + +// Maybe create a struct like StructMetadata for the string list of member and name, ect +pub const struct_name_list: [2][]const u8 = .{ + "User", + "Message", +}; + +pub const struct_member_list: [2][]const []const u8 = .{ + &[_][]const u8{ "name", "email", "age", "scores" }, + &[_][]const u8{"content"}, +}; + +/// Get the list of all member name for a struct name +pub fn structName2structMembers(struct_name: []const u8) []const []const u8 { + var i: u16 = 0; + + while (i < struct_name_list.len) : (i += 1) if (std.mem.eql(u8, struct_name_list[i], struct_name)) break; + + return struct_member_list[i]; +} + +pub fn isStructNameExists(struct_name: []const u8) bool { + for (struct_name_list) |sn| if (std.mem.eql(u8, sn, struct_name)) return true; + return false; +} + +pub fn isMemberNameInStruct(struct_name: []const u8, member_name: []const u8) bool { + for (structName2structMembers(struct_name)) |mn| if (std.mem.eql(u8, mn, member_name)) return true; + return false; +} diff --git a/schema.zipponschema b/src/schema.zipponschema similarity index 100% rename from schema.zipponschema rename to src/schema.zipponschema diff --git a/src/schemaParser.zig b/src/schemaParser.zig index 58c3dd4..2d45a3d 100644 --- a/src/schemaParser.zig +++ b/src/schemaParser.zig @@ -32,6 +32,7 @@ pub const Parser = struct { _ = bytes_written; } + // TODO: Pass that to the DataEngine and do the metadata.zig file instead pub fn parse(self: *Parser, toker: *Toker, buffer: []u8) void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); @@ -39,9 +40,9 @@ pub const Parser = struct { var state: State = .start; - std.fs.cwd().deleteFile("src/dtypes.zig") catch {}; + std.fs.cwd().deleteFile("src/metadata.zig") catch {}; - self.file = std.fs.cwd().createFile("src/dtypes.zig", .{}) catch |err| { + self.file = std.fs.cwd().createFile("src/metadata.zig", .{}) catch |err| { std.debug.print("Error when writing dtypes.zig: {}", .{err}); return; }; diff --git a/src/ziqlTokenizer.zig b/src/ziqlTokenizer.zig index 0e3f10d..9dccbb6 100644 --- a/src/ziqlTokenizer.zig +++ b/src/ziqlTokenizer.zig @@ -18,6 +18,7 @@ pub const Token = struct { .{ "IN", .keyword_in }, .{ "null", .keyword_null }, .{ "__DESCRIBE__", .keyword__describe__ }, + .{ "__INIT__", .keyword__init__ }, }); pub fn getKeyword(bytes: []const u8) ?Tag { @@ -35,6 +36,7 @@ pub const Token = struct { keyword_in, keyword_null, keyword__describe__, + keyword__init__, string_literal, number_literal,