diff --git a/build.zig b/build.zig index 3819532..fb66c93 100644 --- a/build.zig +++ b/build.zig @@ -69,7 +69,7 @@ pub fn build(b: *std.Build) void { const run_tests4 = b.addRunArtifact(tests4); const tests5 = b.addTest(.{ - .root_source_file = b.path("src/ziqlParser.zig"), + .root_source_file = b.path("test.zig"), .target = target, .optimize = optimize, .name = "ZiQL parser", diff --git a/docs/Roadmap.md b/docs/Roadmap.md index 0b5f33c..4251f35 100644 --- a/docs/Roadmap.md +++ b/docs/Roadmap.md @@ -12,12 +12,12 @@ - [X] File engine #### v0.2 - Usable -- [~] Relationships -- [ ] Arrays +- [X] Relationships - [X] Custom data file - [X] Date - [X] Logs - [X] Query multi threading +- [ ] Arrays manipulation #### v0.3 - QoL - [X] Docs website diff --git a/docs/TODO v0.2.md b/docs/TODO v0.2.md index ccbf894..3692bc5 100644 --- a/docs/TODO v0.2.md +++ b/docs/TODO v0.2.md @@ -1,60 +1,8 @@ -- [ ] Delete the .new file if an error happend - [ ] Array manipulation - [ ] Some time keyword like NOW - -Relationships -- [X] Update the schema Parser and Tokenizer -- [X] Include the name of the link struct with the schema_struct -- [X] New ConditionValue that is an array of UUID -- [X] When relationship found in filter, check if the type is right and exist -- [X] When parseFilter, get list of UUID as value for relationship -- [X] Add new operation in Filter evalue: IN and !IN -- [~] parseNewData can use filter like in "Add User (friends = [10] {age > 20})" to return UUID -- [ ] parseFilter can use sub filter. "GRAB User {friends IN {age > 20}}" At least one friend in a list of UUID -- [ ] When send, send the entities in link specify between [] - -Optimizations -- [X] Parse file one time for all conditions, not once per condition -- [X] parse file in parallel, multi threading - - [X] GRAB - - [X] DELETE - - [X] UPDATE -- [ ] Radix Tries ofr UUID list - -ADD User (name='Bob', age = 44, best_friend = {id=0000-0000}) => new_user => UPDATE User {id = 0000-0000} TO (best_friend = new_user) - -GRAB User [friends] {best_friends IN {name = 'Bob'}} - -### Question. How de fuck I am parsing files to get relationships ? -I dont want to parse them more than 3, best 2, perfect 1 - -The issue is that, I could do optimization here but I dont have enough yet. I need to start doing something that work then I will see. -So I parse: -- All files that - -Now this is where the Radix tree come into place. Because if I get to find one UUID in 50000 files, and I parse all of them, this is meh. -So I need a Radix tree to be able to find all file to parse. - -1. Get the list of UUID that need to be parse. - For example if I do "GRAB User [mom] {name = 'Bob'}". I parse one time the file to get all UUID of User that represent mom; the parse that is already done and need to be done. So if I found 3 Bob's mom UUID -2. Then I create a map of Bob's UUID as keys and a Str as value. The Str is the JSON string of the mom. For that I need to parse the file again and write using additional_data +- [X] Benchmark command ## Run in WASM for a demo This could be fun, make a small demo where you get a wasm that run the database locally in the browser. -## How do I return relationship - -So lets say I have a query that get 100 comments. And I return Comment.User. That mean once I parsed all Comments and got all UUID of User in ConditionValue in a map. -I need to get all UUID, meaning concatenating all UUID of all ConditionValue into one map. Then I can parse `User` and create a new map with UUID as key and the JSON string as value. -Like that I can iterate as much as I want inside. - -That mean: - -- If I have a link in AdditionalData to - - Get all UUID that I need the data (concatenate all maps) - - Create a new map UUID/JSON object - - Parse files and populate the new maps - -Which also mean that I need to do all of them at the same time at the beguinning. So using AdditionalData, I iterate over all Nodes, find all Links and do what I said above. -I can then save those map into a map with as key the path like `Comment.friends` and value the map that contain UUID/JSON diff --git a/lib/config.zig b/lib/config.zig index 7575b89..894fd66 100644 --- a/lib/config.zig +++ b/lib/config.zig @@ -2,12 +2,9 @@ pub const BUFFER_SIZE = 1024 * 10; // Used a bit everywhere. The size for the sc pub const MAX_FILE_SIZE = 1024 * 1024; // 1MB pub const CPU_CORE = 16; -// Testing -pub const TEST_DATA_DIR = "data"; - // Debug pub const PRINT_STATE = false; -pub const DONT_SEND = true; +pub const DONT_SEND = false; pub const DONT_SEND_ERROR = false; pub const RESET_LOG_AT_RESTART = false; // If true, will reset the log file at the start of the db, otherwise just keep adding to it diff --git a/lib/types/stringToType.zig b/lib/types/stringToType.zig index 5872612..3477842 100644 --- a/lib/types/stringToType.zig +++ b/lib/types/stringToType.zig @@ -42,6 +42,8 @@ pub fn parseBool(value_str: []const u8) bool { } pub fn parseDate(value_str: []const u8) DateTime { + if (std.mem.eql(u8, value_str, "NOW")) return DateTime.now(); + const year: u16 = std.fmt.parseInt(u16, value_str[0..4], 10) catch 0; const month: u16 = std.fmt.parseInt(u16, value_str[5..7], 10) catch 0; const day: u16 = std.fmt.parseInt(u16, value_str[8..10], 10) catch 0; @@ -74,6 +76,8 @@ pub fn parseArrayDateUnix(allocator: std.mem.Allocator, array_str: []const u8) ! } pub fn parseTime(value_str: []const u8) DateTime { + if (std.mem.eql(u8, value_str, "NOW")) return DateTime.now(); + const hours: u16 = std.fmt.parseInt(u16, value_str[0..2], 10) catch 0; const minutes: u16 = std.fmt.parseInt(u16, value_str[3..5], 10) catch 0; const seconds: u16 = if (value_str.len > 6) std.fmt.parseInt(u16, value_str[6..8], 10) catch 0 else 0; @@ -107,6 +111,8 @@ pub fn parseArrayTimeUnix(allocator: std.mem.Allocator, array_str: []const u8) ! } pub fn parseDatetime(value_str: []const u8) DateTime { + if (std.mem.eql(u8, value_str, "NOW")) return DateTime.now(); + const year: u16 = std.fmt.parseInt(u16, value_str[0..4], 10) catch 0; const month: u16 = std.fmt.parseInt(u16, value_str[5..7], 10) catch 0; const day: u16 = std.fmt.parseInt(u16, value_str[8..10], 10) catch 0; diff --git a/schema/3struct b/schema/3struct new file mode 100644 index 0000000..88bb094 --- /dev/null +++ b/schema/3struct @@ -0,0 +1,23 @@ +User ( + name: str, + age: int, + email: str, + bday: date, + friends: []User, + posts: []Post, + comments: []Comment, +) + +Post ( + text: str, + at: datetime, + from: User, + comments: []Comment, +) + +Comment ( + text: str, + at: datetime, + from: User, + of: Post, +) diff --git a/src/fileEngine.zig b/src/fileEngine.zig index 927946c..ba08754 100644 --- a/src/fileEngine.zig +++ b/src/fileEngine.zig @@ -937,7 +937,6 @@ pub const FileEngine = struct { writer.writeByte(']') catch return FileEngineError.WriteError; } - /// TODO: Delete the file if it is not 0 and is empty at the end fn deleteEntitiesOneFile( sstruct: SchemaStruct, filter: ?Filter, @@ -1027,6 +1026,9 @@ pub const FileEngine = struct { sync_context.completeThread(); } + // TODO: Make a function that take a list of UUID and remove all instance in relationship + // It is to remove when they are deleted + // --------------------ZipponData utils-------------------- //TODO: Update to make it use ConditionValue @@ -1038,8 +1040,8 @@ pub const FileEngine = struct { .unix => |v| return zid.Data.initUnix(v), .str => |v| return zid.Data.initStr(v), .link => |v| { - var iter = v.keyIterator(); if (v.count() > 0) { + var iter = v.keyIterator(); return zid.Data.initUUID(iter.next().?.bytes); } else { const uuid = UUID.parse("00000000-0000-0000-0000-000000000000") catch return ZipponError.InvalidUUID; @@ -1066,7 +1068,7 @@ pub const FileEngine = struct { } /// Take a map from the parseNewData and return an ordered array of Data to be use in a DataWriter - /// TODO: Optimize + /// TODO: Optimize and maybe put it somewhere else than fileEngine fn orderedNewData( self: *FileEngine, allocator: Allocator, @@ -1134,17 +1136,17 @@ pub const FileEngine = struct { return true; } - pub fn writeSchemaFile(self: *FileEngine, null_terminated_schema_buff: [:0]const u8) FileEngineError!void { + pub fn writeSchemaFile(self: *FileEngine, null_terminated_schema_buff: [:0]const u8) ZipponError!void { var zippon_dir = std.fs.cwd().openDir(self.path_to_ZipponDB_dir, .{}) catch return FileEngineError.MemoryError; defer zippon_dir.close(); zippon_dir.deleteFile("schema") catch |err| switch (err) { error.FileNotFound => {}, - else => return FileEngineError.DeleteFileError, + else => return ZipponError.DeleteFileError, }; - var file = zippon_dir.createFile("schema", .{}) catch return FileEngineError.CantMakeFile; + var file = zippon_dir.createFile("schema", .{}) catch return ZipponError.CantMakeFile; defer file.close(); - file.writeAll(null_terminated_schema_buff) catch return FileEngineError.WriteError; + file.writeAll(null_terminated_schema_buff) catch return ZipponError.WriteError; } }; diff --git a/src/schemaEngine.zig b/src/schemaEngine.zig index 8ed59f8..88c3ce6 100644 --- a/src/schemaEngine.zig +++ b/src/schemaEngine.zig @@ -196,6 +196,15 @@ pub const SchemaEngine = struct { 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, diff --git a/src/schemaParser.zig b/src/schemaParser.zig index 9babef5..cf6b8b2 100644 --- a/src/schemaParser.zig +++ b/src/schemaParser.zig @@ -66,13 +66,16 @@ pub const Parser = struct { type_list.append(.self) catch return SchemaParserError.MemoryError; }, .eof => state = .end, - else => return printError( - "Error parsing schema: Expected a struct name", - SchemaParserError.SynthaxError, - self.toker.buffer, - token.loc.start, - token.loc.end, - ), + else => { + std.debug.print("{s}\n", .{self.toker.getTokenSlice(token)}); + return printError( + "Error parsing schema: Expected a struct name", + SchemaParserError.SynthaxError, + self.toker.buffer, + token.loc.start, + token.loc.end, + ); + }, }, .expect_l_paren => switch (token.tag) { @@ -116,6 +119,7 @@ pub const Parser = struct { type_list = std.ArrayList(DataType).init(self.allocator); state = .expect_struct_name_OR_end; + keep_next = true; }, .expect_member_name => { diff --git a/src/stuffs/utils.zig b/src/stuffs/utils.zig index a3ed74b..d416b4d 100644 --- a/src/stuffs/utils.zig +++ b/src/stuffs/utils.zig @@ -90,6 +90,7 @@ pub fn printError(message: []const u8, err: ZipponError, query: ?[]const u8, sta writer.writeAll("\"}") catch {}; send("{s}", .{buffer.items}); + if (config.DONT_SEND and !config.DONT_SEND_ERROR) std.debug.print("{s}", .{buffer.items}); return err; } diff --git a/src/tokenizers/ziql.zig b/src/tokenizers/ziql.zig index 8abc1be..2538b68 100644 --- a/src/tokenizers/ziql.zig +++ b/src/tokenizers/ziql.zig @@ -16,6 +16,11 @@ pub const Token = struct { .{ "OR", .keyword_or }, .{ "TO", .keyword_to }, .{ "NONE", .keyword_none }, + .{ "NOW", .keyword_now }, + .{ "APPEND", .keyword_append }, + .{ "POP", .keyword_pop }, + .{ "REMOVE", .keyword_remove }, + .{ "REMOVEAT", .keyword_remove_at }, .{ "grab", .keyword_grab }, .{ "update", .keyword_update }, .{ "delete", .keyword_delete }, @@ -27,6 +32,7 @@ pub const Token = struct { .{ "none", .keyword_none }, .{ "true", .bool_literal_true }, .{ "false", .bool_literal_false }, + .{ "now", .keyword_now }, }); pub fn getKeyword(bytes: []const u8) ?Tag { @@ -47,6 +53,11 @@ pub const Token = struct { keyword_or, keyword_to, keyword_none, + keyword_now, + keyword_append, + keyword_pop, + keyword_remove, + keyword_remove_at, string_literal, int_literal, diff --git a/src/ziqlParser.zig b/src/ziqlParser.zig index 935396d..833b839 100644 --- a/src/ziqlParser.zig +++ b/src/ziqlParser.zig @@ -370,7 +370,7 @@ pub const Parser = struct { /// Take an array of UUID and populate it with what match what is between {} /// Main is to know if between {} or (), main is true if between {}, otherwise between () inside {} - fn parseFilter(self: Parser, allocator: Allocator, struct_name: []const u8, is_sub: bool) ZipponError!Filter { + pub fn parseFilter(self: Parser, allocator: Allocator, struct_name: []const u8, is_sub: bool) ZipponError!Filter { var filter = try Filter.init(allocator); errdefer filter.deinit(); @@ -492,6 +492,7 @@ pub const Parser = struct { var keep_next = false; var state: State = .expect_member; var token = token_ptr.*; + var member_name: []const u8 = undefined; var condition = Condition{}; @@ -527,6 +528,7 @@ pub const Parser = struct { struct_name, self.toker.getTokenSlice(token), ) catch return ZipponError.MemberNotFound; + member_name = self.toker.getTokenSlice(token); state = .expect_operation; }, else => return printError( @@ -544,7 +546,8 @@ pub const Parser = struct { }, .expect_value => { - condition.value = try self.parseConditionValue(allocator, struct_name, condition.data_type, &token); + log.debug("Parse condition value of member {s}", .{member_name}); + condition.value = try self.parseConditionValue(allocator, struct_name, member_name, condition.data_type, &token); state = .end; }, @@ -664,6 +667,7 @@ pub const Parser = struct { var token = self.toker.next(); var keep_next = false; var state: State = .expect_limit; + var last_member: []const u8 = undefined; while (state != .end) : ({ token = if ((!keep_next) and (state != .end)) self.toker.next() else token; @@ -724,6 +728,7 @@ pub const Parser = struct { self.toker.getTokenSlice(token), try self.schema_engine.memberName2DataIndex(struct_name, self.toker.getTokenSlice(token)), ); + last_member = self.toker.getTokenSlice(token); state = .expect_comma_OR_r_bracket_OR_l_bracket; }, @@ -740,12 +745,11 @@ pub const Parser = struct { .comma => state = .expect_member, .r_bracket => state = .end, .l_bracket => { - // Here now childrens is null, so I need to init it - + const sstruct = try self.schema_engine.structName2SchemaStruct(struct_name); try self.parseAdditionalData( allocator, &additional_data.childrens.items[additional_data.childrens.items.len - 1].additional_data, - struct_name, + sstruct.links.get(last_member).?, ); state = .expect_comma_OR_r_bracket; }, @@ -823,6 +827,7 @@ pub const Parser = struct { .l_bracket, .l_brace, .keyword_none, + .keyword_now, => if (order_full) |o| { if (!o) return printError( "Expected member name.", @@ -868,7 +873,7 @@ pub const Parser = struct { .expect_new_value => { const data_type = self.schema_engine.memberName2DataType(struct_name, member_name) catch return ZipponError.StructNotFound; - map.put(member_name, try self.parseConditionValue(allocator, struct_name, data_type, &token)) catch return ZipponError.MemoryError; + map.put(member_name, try self.parseConditionValue(allocator, struct_name, member_name, data_type, &token)) catch return ZipponError.MemoryError; if (data_type == .link or data_type == .link_array) { token = self.toker.last_token; keep_next = true; @@ -913,23 +918,17 @@ pub const Parser = struct { } /// To run just after a condition like = or > or >= to get the corresponding ConditionValue that you need to compare - fn parseConditionValue(self: Parser, allocator: Allocator, struct_name: []const u8, data_type: dtype.DataType, token: *Token) ZipponError!ConditionValue { + fn parseConditionValue(self: Parser, allocator: Allocator, struct_name: []const u8, member_name: []const u8, data_type: dtype.DataType, token: *Token) ZipponError!ConditionValue { const start_index = token.loc.start; const expected_tag: ?Token.Tag = switch (data_type) { .int => .int_literal, .float => .float_literal, .str => .string_literal, .self => .uuid_literal, - .date => .date_literal, - .time => .time_literal, - .datetime => .datetime_literal, .int_array => .int_literal, .float_array => .float_literal, .str_array => .string_literal, - .date_array => .date_literal, - .time_array => .time_literal, - .datetime_array => .datetime_literal, - .bool, .bool_array, .link, .link_array => null, // handle separately + .bool, .bool_array, .link, .link_array, .date, .time, .datetime, .date_array, .time_array, .datetime_array => null, // handle separately }; // Check if the all next tokens are the right one @@ -948,16 +947,14 @@ pub const Parser = struct { } } } else switch (data_type) { - .bool => { - if (token.tag != .bool_literal_true and token.tag != .bool_literal_false) { - return printError( - "Error: Expected bool", - ZipponError.SynthaxError, - self.toker.buffer, - token.loc.start, - token.loc.end, - ); - } + .bool => if (token.tag != .bool_literal_true and token.tag != .bool_literal_false) { + return printError( + "Error: Expected bool", + ZipponError.SynthaxError, + self.toker.buffer, + token.loc.start, + token.loc.end, + ); }, .bool_array => { token.* = self.toker.next(); @@ -973,6 +970,75 @@ pub const Parser = struct { } } }, + .date => if (token.tag != .date_literal and token.tag != .keyword_now) { + return printError( + "Error: Expected date", + ZipponError.SynthaxError, + self.toker.buffer, + token.loc.start, + token.loc.end, + ); + }, + .date_array => { + token.* = self.toker.next(); + while (token.tag != .r_bracket) : (token.* = self.toker.next()) { + if (token.tag != .date_literal and token.tag != .keyword_now) { + return printError( + "Error: Expected date", + ZipponError.SynthaxError, + self.toker.buffer, + token.loc.start, + token.loc.end, + ); + } + } + }, + .time => if (token.tag != .time_literal and token.tag != .keyword_now) { + return printError( + "Error: Expected time", + ZipponError.SynthaxError, + self.toker.buffer, + token.loc.start, + token.loc.end, + ); + }, + .time_array => { + token.* = self.toker.next(); + while (token.tag != .r_bracket) : (token.* = self.toker.next()) { + if (token.tag != .time_literal and token.tag != .keyword_now) { + return printError( + "Error: Expected time", + ZipponError.SynthaxError, + self.toker.buffer, + token.loc.start, + token.loc.end, + ); + } + } + }, + .datetime => if (token.tag != .datetime_literal and token.tag != .keyword_now) { + return printError( + "Error: Expected datetime", + ZipponError.SynthaxError, + self.toker.buffer, + token.loc.start, + token.loc.end, + ); + }, + .datetime_array => { + token.* = self.toker.next(); + while (token.tag != .r_bracket) : (token.* = self.toker.next()) { + if (token.tag != .datetime_literal and token.tag != .keyword_now) { + return printError( + "Error: Expected datetime", + ZipponError.SynthaxError, + self.toker.buffer, + token.loc.start, + token.loc.end, + ); + } + } + }, .link, .link_array => {}, // TODO: Check if next token is either [ or { else => unreachable, } @@ -1032,7 +1098,13 @@ pub const Parser = struct { token.* = self.toker.next(); } - if (token.tag == .l_brace) filter = try self.parseFilter(allocator, struct_name, false) else return printError( + const link_sstruct = try self.schema_engine.linkedStructName(struct_name, member_name); + std.debug.print("Link SchemaStruct: {s}\n", .{link_sstruct.name}); + if (token.tag == .l_brace) filter = try self.parseFilter( // FIXME: Look like the filter is empty after that (root node is Empty) + allocator, + link_sstruct.name, + false, + ) else return printError( "Error: Expected filter", ZipponError.SynthaxError, self.toker.buffer, @@ -1040,11 +1112,13 @@ pub const Parser = struct { token.loc.end, ); + filter.?.debugPrint(); + // Here I have the filter and additionalData const map = allocator.create(std.AutoHashMap(UUID, void)) catch return ZipponError.MemoryError; map.* = std.AutoHashMap(UUID, void).init(allocator); try self.file_engine.populateVoidUUIDMap( - struct_name, + link_sstruct.name, filter, map, &additional_data, @@ -1080,7 +1154,8 @@ pub const Parser = struct { token.* = self.toker.next(); } - if (token.tag == .l_brace) filter = try self.parseFilter(allocator, struct_name, false) else return printError( + const sstruct = try self.schema_engine.structName2SchemaStruct(struct_name); + if (token.tag == .l_brace) filter = try self.parseFilter(allocator, sstruct.links.get(member_name).?, false) else return printError( "Error: Expected filter", ZipponError.SynthaxError, self.toker.buffer, @@ -1126,176 +1201,3 @@ pub const Parser = struct { return token; } }; - -test "Synthax error" { - try expectParsingError("ADD User (name = 'Bob', email='bob@email.com', age=-55, scores=[ 1 ], best_friend=7db1f06d-a5a7-4917-8cc6-4d490191c9c1, bday=2000/01/01, a_time=12:04:54.8741, last_order=2000/01/01-12:45)", ZipponError.SynthaxError); - try expectParsingError("GRAB {}", ZipponError.StructNotFound); - try expectParsingError("GRAB User {qwe = 'qwe'}", ZipponError.MemberNotFound); - try expectParsingError("ADD User (name='Bob')", ZipponError.MemberMissing); - try expectParsingError("GRAB User {name='Bob'", ZipponError.SynthaxError); - try expectParsingError("GRAB User {age = 50 name='Bob'}", ZipponError.SynthaxError); - try expectParsingError("GRAB User {age <14 AND (age>55}", ZipponError.SynthaxError); - try expectParsingError("GRAB User {name < 'Hello'}", ZipponError.ConditionError); -} - -test "Clear" { - try testParsing("DELETE User {}"); -} - -test "ADD" { - try testParsing("ADD User (name = 'Bob', email='bob@email.com', age=55, scores=[ 1 ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45)"); - try testParsing("ADD User (name = 'Bob', email='bob@email.com', age=55, scores=[ 666 123 331 ], best_friend=none, friends=none, bday=2000/11/01, a_time=12:04:54, last_order=2000/01/01-12:45)"); - try testParsing("ADD User (name = 'Bob', email='bob@email.com', age=-55, scores=[ 33 ], best_friend=none, friends=none, bday=2000/01/04, a_time=12:04:54.8741, last_order=2000/01/01-12:45)"); - try testParsing("ADD User (name = 'Boba', email='boba@email.com', age=20, scores=[ ], best_friend=none, friends=none, bday=2000/06/06, a_time=04:04:54.8741, last_order=2000/01/01-12:45)"); - - try testParsing("ADD User (name = 'Bob', email='bob@email.com', age=-55, scores=[ 1 ], best_friend={name='Bob'}, friends=none, bday=2000/01/01, a_time=12:04:54.8741, last_order=2000/01/01-12:45)"); - try testParsing("ADD User (name = 'Bou', email='bob@email.com', age=66, scores=[ 1 ], best_friend={name = 'Boba'}, friends={name = 'Bob'}, bday=2000/01/01, a_time=02:04:54.8741, last_order=2000/01/01-12:45)"); - try testParsing("ADD User (name = 'Bobibou', email='bob@email.com', age=66, scores=[ 1 ], best_friend={name = 'Boba'}, friends=[1]{name = 'Bob'}, bday=2000/01/01, a_time=02:04:54.8741, last_order=2000/01/01-12:45)"); - - try testParsing("GRAB User {}"); -} - -test "ADD batch" { - try testParsing("ADD User (name = 'ewq', email='ewq@email.com', age=22, scores=[ ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45) (name = 'Roger', email='roger@email.com', age=10, scores=[ 1 11 111 123 562345 123451234 34623465234 12341234 ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45)"); - try testParsing("ADD User (name = 'qwe', email='qwe@email.com', age=57, scores=[ ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45) ('Rodrigo', 'bob@email.com', 55, [ 1 ], {name = 'qwe'}, none, 2000/01/01, 12:04, 2000/01/01-12:45)"); - - try testParsing("GRAB User [name, best_friend] {name = 'Rodrigo'}"); - try testParsing("GRAB User {}"); -} - -test "GRAB filter with string" { - try testParsing("GRAB User {name = 'Bob'}"); - try testParsing("GRAB User {name != 'Brittany Rogers'}"); -} - -test "GRAB with additional data" { - try testParsing("GRAB User [1] {age < 18}"); - try testParsing("GRAB User [id, name] {age < 18}"); - try testParsing("GRAB User [100; name, age] {age < 18}"); -} - -test "UPDATE" { - try testParsing("UPDATE User [1] {name = 'Bob'} TO (email='new@gmail.com')"); - try testParsing("GRAB User {}"); -} - -test "GRAB filter with int" { - try testParsing("GRAB User {age = 18}"); - try testParsing("GRAB User {age > -18}"); - try testParsing("GRAB User {age < 18}"); - try testParsing("GRAB User {age <= 18}"); - try testParsing("GRAB User {age >= 18}"); - try testParsing("GRAB User {age != 18}"); -} - -test "GRAB filter with date" { - try testParsing("GRAB User {bday > 2000/01/01}"); - try testParsing("GRAB User {a_time < 08:00}"); - try testParsing("GRAB User {last_order > 2000/01/01-12:45}"); -} - -test "Specific query" { - try testParsing("GRAB User"); - try testParsing("GRAB User {}"); - try testParsing("GRAB User [1]"); -} - -test "UPDATE relationship" { - try testParsing("UPDATE User [1] {name='Bob'} TO (best_friend = {name='Boba'} )"); - try testParsing("GRAB User {}"); -} - -test "GRAB Relationship Filter" { - try testParsing("GRAB User {best_friend IN {name = 'Bob'}}"); - try testParsing("GRAB User {best_friend IN {name = 'Boba'}}"); -} - -test "GRAB Relationship AdditionalData" { - try testParsing("GRAB User [name, friends] {}"); - try testParsing("GRAB User [name, best_friend] {}"); -} - -test "GRAB Relationship Sub AdditionalData" { - try testParsing("GRAB User [name, friends [name]] {}"); - try testParsing("GRAB User [name, best_friend [name, friends [age]]] {}"); -} - -test "GRAB Relationship AdditionalData Filtered" { - try testParsing("GRAB User [2; name, best_friend] {name = 'Bob'}"); - try testParsing("GRAB User [2; name, best_friend] {best_friend IN {}}"); - try testParsing("GRAB User [2; name, best_friend] {best_friend !IN {}}"); -} - -test "GRAB Relationship dot" { - try testParsing("GRAB User.best_friend {}"); -} - -test "DELETE" { - try testParsing("DELETE User {}"); -} - -const DBEngine = @import("main.zig").DBEngine; - -fn testParsing(source: [:0]const u8) !void { - const TEST_DATA_DIR = @import("config").TEST_DATA_DIR; - - var db_engine = DBEngine.init(TEST_DATA_DIR, null); - defer db_engine.deinit(); - - var toker = Tokenizer.init(source); - var parser = Parser.init( - &toker, - &db_engine.file_engine, - &db_engine.schema_engine, - ); - - try parser.parse(); -} - -fn expectParsingError(source: [:0]const u8, err: ZipponError) !void { - const TEST_DATA_DIR = @import("config").TEST_DATA_DIR; - - var db_engine = DBEngine.init(TEST_DATA_DIR, null); - defer db_engine.deinit(); - - var toker = Tokenizer.init(source); - var parser = Parser.init( - &toker, - &db_engine.file_engine, - &db_engine.schema_engine, - ); - - try std.testing.expectError(err, parser.parse()); -} - -test "Parse filter" { - try testParseFilter("name = 'Adrien'}"); - try testParseFilter("name = 'Adrien' AND age > 11}"); - try testParseFilter("name = 'Adrien' AND (age < 11 OR age > 40)}"); - try testParseFilter("(name = 'Adrien') AND (age < 11 OR age > 40)}"); - try testParseFilter("(name = 'Adrien' OR name = 'Bob') AND (age < 11 OR age > 40)}"); - try testParseFilter("(name = 'Adrien' OR name = 'Bob') AND (age < 11 OR age > 40 AND (age != 20))}"); -} - -fn testParseFilter(source: [:0]const u8) !void { - const TEST_DATA_DIR = @import("config").TEST_DATA_DIR; - - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - - var db_engine = DBEngine.init(TEST_DATA_DIR, null); - defer db_engine.deinit(); - - var toker = Tokenizer.init(source); - var parser = Parser.init( - &toker, - &db_engine.file_engine, - &db_engine.schema_engine, - ); - - var filter = try parser.parseFilter(allocator, "User", false); - defer filter.deinit(); - std.debug.print("{s}\n", .{source}); - filter.debugPrint(); -} diff --git a/test.zig b/test.zig new file mode 100644 index 0000000..a8c8f33 --- /dev/null +++ b/test.zig @@ -0,0 +1,205 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Parser = @import("src/ziqlParser.zig").Parser; +const Tokenizer = @import("src/tokenizers/ziql.zig").Tokenizer; +const DBEngine = @import("src/main.zig").DBEngine; +const ZipponError = @import("src/stuffs/errors.zig").ZipponError; + +const DB = struct { + path: []const u8, + schema: []const u8, +}; + +test "Synthax error" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try expectParsingError(db, "ADD User (name = 'Bob', email='bob@email.com', age=-55, scores=[ 1 ], best_friend=7db1f06d-a5a7-4917-8cc6-4d490191c9c1, bday=2000/01/01, a_time=12:04:54.8741, last_order=2000/01/01-12:45)", ZipponError.SynthaxError); + try expectParsingError(db, "GRAB {}", ZipponError.StructNotFound); + try expectParsingError(db, "GRAB User {qwe = 'qwe'}", ZipponError.MemberNotFound); + try expectParsingError(db, "ADD User (name='Bob')", ZipponError.MemberMissing); + try expectParsingError(db, "GRAB User {name='Bob'", ZipponError.SynthaxError); + try expectParsingError(db, "GRAB User {age = 50 name='Bob'}", ZipponError.SynthaxError); + try expectParsingError(db, "GRAB User {age <14 AND (age>55}", ZipponError.SynthaxError); + try expectParsingError(db, "GRAB User {name < 'Hello'}", ZipponError.ConditionError); +} + +test "Clear" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "DELETE User {}"); +} + +test "ADD" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "ADD User (name = 'Bob', email='bob@email.com', age=55, scores=[ 1 ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45)"); + try testParsing(db, "ADD User (name = 'Bob', email='bob@email.com', age=55, scores=[ 666 123 331 ], best_friend=none, friends=none, bday=2000/11/01, a_time=12:04:54, last_order=2000/01/01-12:45)"); + try testParsing(db, "ADD User (name = 'Bob', email='bob@email.com', age=-55, scores=[ 33 ], best_friend=none, friends=none, bday=2000/01/04, a_time=12:04:54.8741, last_order=2000/01/01-12:45)"); + try testParsing(db, "ADD User (name = 'Boba', email='boba@email.com', age=20, scores=[ ], best_friend=none, friends=none, bday=2000/06/06, a_time=04:04:54.8741, last_order=2000/01/01-12:45)"); + + try testParsing(db, "ADD User (name = 'Bob', email='bob@email.com', age=-55, scores=[ 1 ], best_friend={name='Bob'}, friends=none, bday=2000/01/01, a_time=12:04:54.8741, last_order=2000/01/01-12:45)"); + try testParsing(db, "ADD User (name = 'Bou', email='bob@email.com', age=66, scores=[ 1 ], best_friend={name = 'Boba'}, friends={name = 'Bob'}, bday=2000/01/01, a_time=02:04:54.8741, last_order=2000/01/01-12:45)"); + try testParsing(db, "ADD User (name = 'Bobibou', email='bob@email.com', age=66, scores=[ 1 ], best_friend={name = 'Boba'}, friends=[1]{name = 'Bob'}, bday=2000/01/01, a_time=02:04:54.8741, last_order=2000/01/01-12:45)"); + + try testParsing(db, "GRAB User {}"); +} + +test "ADD batch" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "ADD User (name = 'ewq', email='ewq@email.com', age=22, scores=[ ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45) (name = 'Roger', email='roger@email.com', age=10, scores=[ 1 11 111 123 562345 123451234 34623465234 12341234 ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45)"); + try testParsing(db, "ADD User (name = 'qwe', email='qwe@email.com', age=57, scores=[ ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45) ('Rodrigo', 'bob@email.com', 55, [ 1 ], {name = 'qwe'}, none, 2000/01/01, 12:04, 2000/01/01-12:45)"); + + try testParsing(db, "GRAB User [name, best_friend] {name = 'Rodrigo'}"); + try testParsing(db, "GRAB User {}"); +} + +test "GRAB filter with string" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User {name = 'Bob'}"); + try testParsing(db, "GRAB User {name != 'Brittany Rogers'}"); +} + +test "GRAB with additional data" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User [1] {age < 18}"); + try testParsing(db, "GRAB User [id, name] {age < 18}"); + try testParsing(db, "GRAB User [100; name, age] {age < 18}"); +} + +test "UPDATE" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "UPDATE User [1] {name = 'Bob'} TO (email='new@gmail.com')"); + try testParsing(db, "GRAB User {}"); +} + +test "GRAB filter with int" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User {age = 18}"); + try testParsing(db, "GRAB User {age > -18}"); + try testParsing(db, "GRAB User {age < 18}"); + try testParsing(db, "GRAB User {age <= 18}"); + try testParsing(db, "GRAB User {age >= 18}"); + try testParsing(db, "GRAB User {age != 18}"); +} + +test "GRAB filter with date" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User {bday > 2000/01/01}"); + try testParsing(db, "GRAB User {a_time < 08:00}"); + try testParsing(db, "GRAB User {last_order > 2000/01/01-12:45}"); +} + +test "Specific query" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User"); + try testParsing(db, "GRAB User {}"); + try testParsing(db, "GRAB User [1]"); +} + +test "UPDATE relationship" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "UPDATE User [1] {name='Bob'} TO (best_friend = {name='Boba'} )"); + try testParsing(db, "GRAB User {}"); +} + +test "GRAB Relationship Filter" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User {best_friend IN {name = 'Bob'}}"); + try testParsing(db, "GRAB User {best_friend IN {name = 'Boba'}}"); +} + +test "GRAB Relationship AdditionalData" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User [name, friends] {}"); + try testParsing(db, "GRAB User [name, best_friend] {}"); +} + +test "GRAB Relationship Sub AdditionalData" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User [name, friends [name]] {}"); + try testParsing(db, "GRAB User [name, best_friend [name, friends [age]]] {}"); +} + +test "GRAB Relationship AdditionalData Filtered" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User [2; name, best_friend] {name = 'Bob'}"); + try testParsing(db, "GRAB User [2; name, best_friend] {best_friend IN {}}"); + try testParsing(db, "GRAB User [2; name, best_friend] {best_friend !IN {}}"); +} + +test "GRAB Relationship dot" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "GRAB User.best_friend {}"); +} + +test "DELETE" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParsing(db, "DELETE User {}"); +} + +test "3 struct ADD" { + const db = DB{ .path = "test2", .schema = "schema/3struct" }; + try testParsing(db, "DELETE User {}"); + try testParsing(db, "DELETE Post {}"); + try testParsing(db, "ADD User (name = 'Bob', email='bob@email.com', age=55, friends=none, posts=none, comments=none, bday=2000/01/01)"); + try testParsing(db, "ADD Post (text = 'Hello every body', at=NOW, from={}, comments=none)"); + try testParsing(db, "ADD Post (text = 'Hello every body', at=NOW, from={}, comments=none)"); + try testParsing(db, "GRAB Post [id, text, at, from [id, name]] {}"); + try testParsing(db, "GRAB User [id, name] {}"); +} + +fn testParsing(db: DB, source: [:0]const u8) !void { + var db_engine = DBEngine.init(db.path, db.schema); + defer db_engine.deinit(); + + var toker = Tokenizer.init(source); + var parser = Parser.init( + &toker, + &db_engine.file_engine, + &db_engine.schema_engine, + ); + + try parser.parse(); +} + +fn expectParsingError(db: DB, source: [:0]const u8, err: ZipponError) !void { + var db_engine = DBEngine.init(db.path, db.schema); + defer db_engine.deinit(); + + var toker = Tokenizer.init(source); + var parser = Parser.init( + &toker, + &db_engine.file_engine, + &db_engine.schema_engine, + ); + + try std.testing.expectError(err, parser.parse()); +} + +test "Parse filter" { + const db = DB{ .path = "test1", .schema = "schema/example" }; + try testParseFilter(db, "name = 'Adrien'}"); + try testParseFilter(db, "name = 'Adrien' AND age > 11}"); + try testParseFilter(db, "name = 'Adrien' AND (age < 11 OR age > 40)}"); + try testParseFilter(db, "(name = 'Adrien') AND (age < 11 OR age > 40)}"); + try testParseFilter(db, "(name = 'Adrien' OR name = 'Bob') AND (age < 11 OR age > 40)}"); + try testParseFilter(db, "(name = 'Adrien' OR name = 'Bob') AND (age < 11 OR age > 40 AND (age != 20))}"); +} + +fn testParseFilter(db: DB, source: [:0]const u8) !void { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + var db_engine = DBEngine.init(db.path, db.schema); + defer db_engine.deinit(); + + var toker = Tokenizer.init(source); + var parser = Parser.init( + &toker, + &db_engine.file_engine, + &db_engine.schema_engine, + ); + + var filter = try parser.parseFilter(allocator, "User", false); + defer filter.deinit(); + std.debug.print("{s}\n", .{source}); + filter.debugPrint(); +}