Started to debug schema with multiple struct and some time keyword

Added NOW already and now debuging some stuff regarding filter and
parsing file of one struct when it should be another

Also moved query test into a seperated test file.
And some fix and changed in docs
This commit is contained in:
Adrien Bouvais 2025-01-08 10:09:15 +01:00
parent a712fe3e83
commit 71e5f6eb1e
13 changed files with 382 additions and 274 deletions

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

23
schema/3struct Normal file
View File

@ -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,
)

View File

@ -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;
}
};

View File

@ -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,

View File

@ -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 => {

View File

@ -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;
}

View File

@ -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,

View File

@ -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();
}

205
test.zig Normal file
View File

@ -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();
}