ZipponDB/src/ziqlParser.zig

1205 lines
50 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const FileEngine = @import("fileEngine.zig").FileEngine;
const SchemaEngine = @import("schemaEngine.zig").SchemaEngine;
const Tokenizer = @import("tokenizers/ziql.zig").Tokenizer;
const Token = @import("tokenizers/ziql.zig").Token;
const dtype = @import("dtype");
const UUID = dtype.UUID;
const Filter = @import("stuffs/filter.zig").Filter;
const Condition = @import("stuffs/filter.zig").Condition;
const ConditionValue = @import("stuffs/filter.zig").ConditionValue;
const ComparisonOperator = @import("stuffs/filter.zig").ComparisonOperator;
const AdditionalData = @import("stuffs/additionalData.zig").AdditionalData;
const AdditionalDataMember = @import("stuffs/additionalData.zig").AdditionalDataMember;
const send = @import("stuffs/utils.zig").send;
const printError = @import("stuffs/utils.zig").printError;
const ZiQlParserError = @import("stuffs/errors.zig").ZiQlParserError;
const ZipponError = @import("stuffs/errors.zig").ZipponError;
const PRINT_STATE = @import("config.zig").PRINT_STATE;
const log = std.log.scoped(.ziqlParser);
const State = enum {
start,
invalid,
end,
// Endpoint
parse_new_data_and_add_data,
filter_and_send,
filter_and_update,
filter_and_delete,
// For the main parse function
expect_struct_name,
expect_filter,
parse_additional_data,
expect_filter_or_additional_data,
expect_new_data,
expect_right_arrow,
// For the additional data parser
expect_count_of_entity_to_find,
expect_semicolon_OR_right_bracket,
expect_member,
expect_comma_OR_r_bracket_OR_l_bracket,
expect_comma_OR_r_bracket,
// For the filter parser
expect_condition,
expect_operation, // Operations are = != < <= > >=
expect_value,
expect_ANDOR_OR_end,
expect_right_uuid_array,
// For the new data
expect_equal,
expect_new_value,
expect_comma_OR_end,
add_member_to_map,
add_array_to_map,
};
pub const Parser = struct {
toker: *Tokenizer,
file_engine: *FileEngine,
schema_engine: *SchemaEngine,
// TODO: Improve memory management, stop using an alloc in init maybe
pub fn init(toker: *Tokenizer, file_engine: *FileEngine, schema_engine: *SchemaEngine) Parser {
// Do I need to init a FileEngine at each Parser, can't I put it in the CLI parser instead ?
return Parser{
.toker = toker,
.file_engine = file_engine,
.schema_engine = schema_engine,
};
}
pub fn parse(self: Parser) ZipponError!void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var state: State = .start;
var additional_data = AdditionalData.init(allocator);
defer additional_data.deinit();
var struct_name: []const u8 = undefined;
var action: enum { GRAB, ADD, UPDATE, DELETE } = undefined;
var token = self.toker.next();
var keep_next = false; // Use in the loop to prevent to get the next token when continue. Just need to make it true and it is reset at every loop
while (state != State.end) : ({
token = if (!keep_next) self.toker.next() else token;
keep_next = false;
if (PRINT_STATE) std.debug.print("parse: {any}\n", .{state});
}) switch (state) {
.start => switch (token.tag) {
.keyword_grab => {
action = .GRAB;
state = .expect_struct_name;
},
.keyword_add => {
action = .ADD;
state = .expect_struct_name;
},
.keyword_update => {
action = .UPDATE;
state = .expect_struct_name;
},
.keyword_delete => {
action = .DELETE;
state = .expect_struct_name;
},
else => return printError(
"Error: Expected action keyword. Available: GRAB ADD DELETE UPDATE",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.expect_struct_name => {
// Check if the struct name is in the schema
struct_name = self.toker.getTokenSlice(token);
if (token.tag != .identifier) return printError(
"Error: Missing struct name",
ZiQlParserError.StructNotFound,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
if (!self.schema_engine.isStructNameExists(struct_name)) return printError(
"Error: struct name not found in schema.",
ZiQlParserError.StructNotFound,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
switch (action) {
.ADD => state = .expect_new_data,
else => state = .expect_filter_or_additional_data,
}
},
.expect_filter_or_additional_data => {
keep_next = true;
switch (token.tag) {
.l_bracket => state = .parse_additional_data,
.l_brace => state = switch (action) {
.GRAB => .filter_and_send,
.UPDATE => .filter_and_update,
.DELETE => .filter_and_delete,
else => unreachable,
},
.eof => state = .filter_and_send,
else => return printError(
"Error: Expect [ for additional data or { for a filter",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
}
},
.parse_additional_data => {
try self.parseAdditionalData(allocator, &additional_data, struct_name);
state = switch (action) {
.GRAB => .filter_and_send,
.UPDATE => .filter_and_update,
.DELETE => .filter_and_delete,
else => unreachable,
};
},
.filter_and_send => switch (token.tag) {
.l_brace => {
var filter = try self.parseFilter(allocator, struct_name, false);
defer filter.deinit();
var buff = std.ArrayList(u8).init(allocator);
defer buff.deinit();
try self.file_engine.parseEntities(struct_name, filter, &additional_data, &buff.writer());
send("{s}", .{buff.items});
state = .end;
},
.eof => {
var buff = std.ArrayList(u8).init(allocator);
defer buff.deinit();
try self.file_engine.parseEntities(struct_name, null, &additional_data, &buff.writer());
send("{s}", .{buff.items});
state = .end;
},
else => return printError(
"Error: Expected filter.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
// TODO: Optimize so it doesnt use parseFilter but just parse the file and directly check the condition. Here I end up parsing 2 times.
.filter_and_update => switch (token.tag) {
.l_brace => {
var filter = try self.parseFilter(allocator, struct_name, false);
defer filter.deinit();
token = self.toker.last();
if (token.tag != .keyword_to) return printError(
"Error: Expected TO",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
token = self.toker.next();
if (token.tag != .l_paren) return printError(
"Error: Expected (",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
var data_map = std.StringHashMap(ConditionValue).init(allocator);
defer data_map.deinit();
try self.parseNewData(allocator, &data_map, struct_name);
var buff = std.ArrayList(u8).init(allocator);
defer buff.deinit();
try self.file_engine.updateEntities(struct_name, filter, data_map, &buff.writer(), &additional_data);
send("{s}", .{buff.items});
state = .end;
},
.keyword_to => {
token = self.toker.next();
if (token.tag != .l_paren) return printError(
"Error: Expected (",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
var data_map = std.StringHashMap(ConditionValue).init(allocator);
defer data_map.deinit();
try self.parseNewData(allocator, &data_map, struct_name);
var buff = std.ArrayList(u8).init(allocator);
defer buff.deinit();
try self.file_engine.updateEntities(struct_name, null, data_map, &buff.writer(), &additional_data);
send("{s}", .{buff.items});
state = .end;
},
else => return printError(
"Error: Expected filter or TO.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.filter_and_delete => switch (token.tag) {
.l_brace => {
var filter = try self.parseFilter(allocator, struct_name, false);
defer filter.deinit();
var buff = std.ArrayList(u8).init(allocator);
defer buff.deinit();
try self.file_engine.deleteEntities(struct_name, filter, &buff.writer(), &additional_data);
send("{s}", .{buff.items});
state = .end;
},
.eof => {
var buff = std.ArrayList(u8).init(allocator);
defer buff.deinit();
try self.file_engine.deleteEntities(struct_name, null, &buff.writer(), &additional_data);
send("{s}", .{buff.items});
state = .end;
},
else => return printError(
"Error: Expected filter.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.expect_new_data => switch (token.tag) {
.l_paren => {
keep_next = true;
state = .parse_new_data_and_add_data;
},
else => return printError(
"Error: Expected new data starting with (",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.parse_new_data_and_add_data => {
var data_map = std.StringHashMap(ConditionValue).init(allocator);
defer data_map.deinit();
try self.parseNewData(allocator, &data_map, struct_name);
var error_message_buffer = std.ArrayList(u8).init(allocator);
defer error_message_buffer.deinit();
const error_message_buffer_writer = error_message_buffer.writer();
error_message_buffer_writer.writeAll("Error missing: ") catch return ZipponError.WriteError;
if (!(self.schema_engine.checkIfAllMemberInMap(struct_name, &data_map, &error_message_buffer) catch {
return ZiQlParserError.StructNotFound;
})) {
_ = error_message_buffer.pop();
_ = error_message_buffer.pop();
return printError(
error_message_buffer.items,
ZiQlParserError.MemberMissing,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
var buff = std.ArrayList(u8).init(allocator);
defer buff.deinit();
token = self.toker.last_token;
if (token.tag == .identifier and std.mem.eql(u8, self.toker.getTokenSlice(token), "TESTDATASET")) {
for (0..100) |_| self.file_engine.addEntity(struct_name, data_map, &buff.writer(), 10_000) catch return ZipponError.CantWriteEntity;
} else {
self.file_engine.addEntity(struct_name, data_map, &buff.writer(), 1) catch return ZipponError.CantWriteEntity;
}
send("{s}", .{buff.items});
state = .end;
},
else => unreachable,
};
}
/// 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 {
var filter = try Filter.init(allocator);
errdefer filter.deinit();
var keep_next = false;
var token = self.toker.next();
var state: State = .expect_condition;
while (state != .end) : ({
token = if (keep_next) token else self.toker.next();
keep_next = false;
if (PRINT_STATE) std.debug.print("parseFilter: {any}\n", .{state});
})
switch (state) {
.expect_condition => switch (token.tag) {
.r_brace => {
if (!is_sub) {
state = .end;
} else {
return printError(
"Error: Expected ) not }",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
},
.r_paren => {
if (is_sub) {
state = .end;
} else {
return printError(
"Error: Expected } not )",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
},
.l_paren => {
var sub_filter = try self.parseFilter(allocator, struct_name, true);
filter.addSubFilter(&sub_filter);
token = self.toker.last();
keep_next = true;
state = .expect_ANDOR_OR_end;
},
.identifier => {
const condition = try self.parseCondition(allocator, &token, struct_name);
try filter.addCondition(condition);
token = self.toker.last();
keep_next = true;
state = .expect_ANDOR_OR_end;
},
else => return printError(
"Error: Expected ( or condition.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.expect_ANDOR_OR_end => switch (token.tag) {
.r_brace => {
if (!is_sub) {
state = .end;
} else {
return printError(
"Error: Expected ) not }",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
},
.r_paren => {
if (is_sub) {
state = .end;
} else {
return printError(
"Error: Expected } not )",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
},
.keyword_and => {
try filter.addLogicalOperator(.AND);
state = .expect_condition;
},
.keyword_or => {
try filter.addLogicalOperator(.OR);
state = .expect_condition;
},
else => return printError(
"Error: Expected AND, OR, or }",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.end => {},
else => unreachable,
};
return filter;
}
/// Parse to get a Condition. Which is a struct that is use by the FileEngine to retreive data.
/// In the query, it is this part name = 'Bob' or age <= 10
fn parseCondition(self: Parser, allocator: Allocator, token_ptr: *Token, struct_name: []const u8) ZipponError!Condition {
var keep_next = false;
var state: State = .expect_member;
var token = token_ptr.*;
var condition = Condition{};
while (state != .end) : ({
token = if (!keep_next) self.toker.next() else token;
keep_next = false;
if (PRINT_STATE) std.debug.print("parseCondition: {any}\n", .{state});
}) switch (state) {
.expect_member => switch (token.tag) {
.identifier => {
if (!(self.schema_engine.isMemberNameInStruct(struct_name, self.toker.getTokenSlice(token)) catch {
return printError(
"Error: Struct not found.",
ZiQlParserError.StructNotFound,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
})) {
return printError(
"Error: Member not part of struct.",
ZiQlParserError.MemberNotFound,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
condition.data_type = self.schema_engine.memberName2DataType(
struct_name,
self.toker.getTokenSlice(token),
) catch return ZiQlParserError.MemberNotFound;
condition.data_index = self.schema_engine.memberName2DataIndex(
struct_name,
self.toker.getTokenSlice(token),
) catch return ZiQlParserError.MemberNotFound;
state = .expect_operation;
},
else => return printError(
"Error: Expected member name.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.expect_operation => {
condition.operation = try self.parseComparisonOperator(token);
state = .expect_value;
},
.expect_value => {
condition.value = try self.parseConditionValue(allocator, struct_name, condition.data_type, &token);
state = .end;
},
else => unreachable,
};
try self.checkConditionValidity(condition, token);
return condition;
}
/// Will check if what is compared is ok, like comparing if a string is superior to another string is not for example.
fn checkConditionValidity(self: Parser, condition: Condition, token: Token) ZipponError!void {
switch (condition.operation) {
.equal => switch (condition.data_type) {
.int, .float, .str, .bool, .link, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, str, bool, date, time, datetime can be compare with =.",
ZiQlParserError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.different => switch (condition.data_type) {
.int, .float, .str, .bool, .link, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, str, bool, date, time, datetime can be compare with !=.",
ZiQlParserError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.superior_or_equal => switch (condition.data_type) {
.int, .float, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, date, time, datetime can be compare with >=.",
ZiQlParserError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.superior => switch (condition.data_type) {
.int, .float, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, date, time, datetime can be compare with >.",
ZiQlParserError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.inferior_or_equal => switch (condition.data_type) {
.int, .float, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, date, time, datetime can be compare with <=.",
ZiQlParserError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.inferior => switch (condition.data_type) {
.int, .float, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, date, time, datetime can be compare with <.",
ZiQlParserError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.in => switch (condition.data_type) {
.link => {},
else => return printError(
"Error: Only link can be compare with in for now.",
ZiQlParserError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
else => unreachable,
}
}
/// When this function is call, next token should be [
/// Check if an int is here -> check if ; is here -> check if member is here -> check if [ is here -> loop
fn parseAdditionalData(self: Parser, allocator: Allocator, additional_data: *AdditionalData, struct_name: []const u8) ZipponError!void {
var token = self.toker.next();
var keep_next = false;
var state: State = .expect_count_of_entity_to_find;
while (state != .end) : ({
token = if ((!keep_next) and (state != .end)) self.toker.next() else token;
keep_next = false;
if (PRINT_STATE) std.debug.print("parseAdditionalData: {any}\n", .{state});
}) switch (state) {
.expect_count_of_entity_to_find => switch (token.tag) {
.int_literal => {
const count = std.fmt.parseInt(usize, self.toker.getTokenSlice(token), 10) catch {
return printError(
"Error while transforming this into a integer.",
ZiQlParserError.ParsingValueError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
};
additional_data.limit = count;
state = .expect_semicolon_OR_right_bracket;
},
else => {
state = .expect_member;
keep_next = true;
},
},
.expect_semicolon_OR_right_bracket => switch (token.tag) {
.semicolon => state = .expect_member,
.r_bracket => state = .end,
else => return printError(
"Error: Expect ';' or ']'.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.expect_member => switch (token.tag) {
.identifier => {
if (!(self.schema_engine.isMemberNameInStruct(struct_name, self.toker.getTokenSlice(token)) catch {
return printError(
"Struct not found.",
ZiQlParserError.StructNotFound,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
})) {
return printError(
"Member not found in struct.",
ZiQlParserError.MemberNotFound,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
additional_data.childrens.append(
AdditionalDataMember.init(
allocator,
self.toker.getTokenSlice(token),
try self.schema_engine.memberName2DataIndex(struct_name, self.toker.getTokenSlice(token)),
),
) catch return ZipponError.MemoryError;
state = .expect_comma_OR_r_bracket_OR_l_bracket;
},
else => return printError(
"Error: Expected a member name.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.expect_comma_OR_r_bracket_OR_l_bracket => switch (token.tag) {
.comma => state = .expect_member,
.r_bracket => state = .end,
.l_bracket => {
try self.parseAdditionalData(
allocator,
&additional_data.childrens.items[additional_data.childrens.items.len - 1].additional_data,
struct_name,
);
state = .expect_comma_OR_r_bracket;
},
else => return printError(
"Error: Expected , or ] or [",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.expect_comma_OR_r_bracket => switch (token.tag) {
.comma => state = .expect_member,
.r_bracket => state = .end,
else => return printError(
"Error: Expected , or ]",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
else => unreachable,
};
}
/// Take the tokenizer and return a map of the ADD action.
/// Keys are the member name and value are the string of the value in the query. E.g. 'Adrien' or '10'
/// Entry token need to be (
fn parseNewData(self: Parser, allocator: Allocator, map: *std.StringHashMap(ConditionValue), struct_name: []const u8) !void {
var token = self.toker.next();
var keep_next = false;
var member_name: []const u8 = undefined; // Maybe use allocator.alloc
var state: State = .expect_member;
while (state != .end) : ({
token = if (!keep_next) self.toker.next() else token;
keep_next = false;
if (PRINT_STATE) std.debug.print("parseNewData: {any}\n", .{state});
}) switch (state) {
.expect_member => switch (token.tag) {
.identifier => {
member_name = self.toker.getTokenSlice(token);
if (!(self.schema_engine.isMemberNameInStruct(struct_name, member_name) catch {
return ZiQlParserError.StructNotFound;
})) return printError(
"Member not found in struct.",
ZiQlParserError.MemberNotFound,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
state = .expect_equal;
},
else => return printError(
"Error: Expected member name.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.expect_equal => switch (token.tag) {
// TODO: Implement stuff to manipulate array like APPEND or REMOVE
.equal => state = .expect_new_value,
else => return printError(
"Error: Expected =",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.expect_new_value => {
const data_type = self.schema_engine.memberName2DataType(struct_name, member_name) catch return ZiQlParserError.StructNotFound;
std.debug.print("DATA TYPE: {any}\n", .{data_type});
map.put(member_name, try self.parseConditionValue(allocator, struct_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;
}
state = .expect_comma_OR_end;
},
.expect_comma_OR_end => switch (token.tag) {
.r_paren => state = .end,
.comma => state = .expect_member,
else => return printError(
"Error: Expect , or )",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
else => unreachable,
};
}
fn parseComparisonOperator(self: Parser, token: Token) ZipponError!ComparisonOperator {
return switch (token.tag) {
.equal => .equal, // =
.angle_bracket_left => .inferior, // <
.angle_bracket_right => .superior, // >
.angle_bracket_left_equal => .inferior_or_equal, // <=
.angle_bracket_right_equal => .superior_or_equal, // >=
.bang_equal => .different, // !=
.keyword_in => .in,
.keyword_not_in => .not_in,
else => return printError(
"Error: Expected condition. Including < > <= >= = !=",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
};
}
/// 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 {
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
};
// Check if the all next tokens are the right one
if (expected_tag) |tag| {
if (data_type.is_array()) {
token.* = try self.checkTokensInArray(tag);
} else {
if (token.tag != tag) {
return printError(
"Error: Wrong type", // TODO: Print the expected type
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
}
} else switch (data_type) {
.bool => {
if (token.tag != .bool_literal_true and token.tag != .bool_literal_false) {
return printError(
"Error: Expected bool",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
},
.bool_array => {
token.* = self.toker.next();
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (token.tag != .bool_literal_true and token.tag != .bool_literal_false) {
return printError(
"Error: Expected bool or ]",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
}
},
.link, .link_array => {}, // TODO: Check if next token is either [ or {
else => unreachable,
}
// And finally create the ConditionValue
var value: ConditionValue = undefined;
switch (data_type) {
.int => value = ConditionValue.initInt(self.toker.buffer[start_index..token.loc.end]),
.float => value = ConditionValue.initFloat(self.toker.buffer[start_index..token.loc.end]),
.str => value = ConditionValue.initStr(self.toker.buffer[start_index + 1 .. token.loc.end - 1]),
.date => value = ConditionValue.initDate(self.toker.buffer[start_index..token.loc.end]),
.time => value = ConditionValue.initTime(self.toker.buffer[start_index..token.loc.end]),
.datetime => value = ConditionValue.initDateTime(self.toker.buffer[start_index..token.loc.end]),
.bool => value = ConditionValue.initBool(self.toker.buffer[start_index..token.loc.end]),
.int_array => value = try ConditionValue.initArrayInt(allocator, self.toker.buffer[start_index..token.loc.end]),
.str_array => value = try ConditionValue.initArrayStr(allocator, self.toker.buffer[start_index..token.loc.end]),
.bool_array => value = try ConditionValue.initArrayBool(allocator, self.toker.buffer[start_index..token.loc.end]),
.float_array => value = try ConditionValue.initArrayFloat(allocator, self.toker.buffer[start_index..token.loc.end]),
.date_array => value = try ConditionValue.initArrayDate(allocator, self.toker.buffer[start_index..token.loc.end]),
.time_array => value = try ConditionValue.initArrayTime(allocator, self.toker.buffer[start_index..token.loc.end]),
.datetime_array => value = try ConditionValue.initArrayDateTime(allocator, self.toker.buffer[start_index..token.loc.end]),
.link => switch (token.tag) {
.keyword_none => {
const map = allocator.create(std.AutoHashMap(UUID, void)) catch return ZipponError.MemoryError;
map.* = std.AutoHashMap(UUID, void).init(allocator);
_ = map.getOrPut(UUID.parse("00000000-0000-0000-0000-000000000000") catch @panic("Sorry wot ?")) catch return ZipponError.MemoryError;
value = ConditionValue.initLink(map);
_ = self.toker.next();
},
.uuid_literal => {
const uuid = UUID.parse(self.toker.buffer[start_index..token.loc.end]) catch return ZipponError.InvalidUUID;
if (!self.schema_engine.isUUIDExist(struct_name, uuid)) return printError(
"Error: UUID do not exist in database.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
const map = allocator.create(std.AutoHashMap(UUID, void)) catch return ZipponError.MemoryError;
map.* = std.AutoHashMap(UUID, void).init(allocator);
_ = map.getOrPut(uuid) catch return ZipponError.MemoryError;
value = ConditionValue.initLink(map);
_ = self.toker.next();
},
.l_brace, .l_bracket => {
var filter: ?Filter = null;
defer if (filter != null) filter.?.deinit();
var additional_data = AdditionalData.init(allocator);
defer additional_data.deinit();
if (token.tag == .l_bracket) {
try self.parseAdditionalData(allocator, &additional_data, struct_name);
token.* = self.toker.next();
}
additional_data.limit = 1;
if (token.tag == .l_brace) filter = try self.parseFilter(allocator, struct_name, false) else return printError(
"Error: Expected filter",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
// 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,
filter,
map,
&additional_data,
);
value = ConditionValue.initLink(map);
},
else => return printError(
"Error: Expected uuid or none",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.link_array => switch (token.tag) {
.keyword_none => {
const map = allocator.create(std.AutoHashMap(UUID, void)) catch return ZipponError.MemoryError;
map.* = std.AutoHashMap(UUID, void).init(allocator);
value = ConditionValue.initArrayLink(map);
_ = self.toker.next();
},
.l_brace, .l_bracket => {
var filter: ?Filter = null;
defer if (filter != null) filter.?.deinit();
var additional_data = AdditionalData.init(allocator);
defer additional_data.deinit();
if (token.tag == .l_bracket) {
try self.parseAdditionalData(allocator, &additional_data, struct_name);
token.* = self.toker.next();
}
if (token.tag == .l_brace) filter = try self.parseFilter(allocator, struct_name, false) else return printError(
"Error: Expected filter",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
// 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,
filter,
map,
&additional_data,
);
value = ConditionValue.initArrayLink(map);
},
else => return printError(
"Error: Expected uuid or none",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.self => unreachable,
}
return value;
}
/// Check if all token in an array is of one specific type
fn checkTokensInArray(self: Parser, tag: Token.Tag) ZipponError!Token {
var token = self.toker.next();
while (token.tag != .r_bracket) : (token = self.toker.next()) {
if (token.tag != tag) return printError(
"Error: Wrong type.",
ZiQlParserError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
return token;
}
};
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)");
// This need to take the first User named Bob as it is a unique link
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: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 "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 < 18}");
}
test "UPDATE" {
try testParsing("UPDATE User {name = 'Bob'} TO (email='new@gmail.com')");
}
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]");
}
// TODO: next step is to make this work
test "UPDATE relationship" {
try testParsing("UPDATE User [1] {name='Bob'} TO (best_friend = {name='Boba'} )");
try testParsing("GRAB User {}");
}
// Not yet working but dont trow an error
test "GRAB Relationship" {
try testParsing("GRAB User {best_friend IN {name = 'Bob'}}");
try testParsing("GRAB User {best_friend IN {name = 'Boba'}}");
}
test "DELETE" {
try testParsing("DELETE User {}");
}
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)", ZiQlParserError.SynthaxError);
try expectParsingError("GRAB {}", ZiQlParserError.StructNotFound);
try expectParsingError("GRAB User {qwe = 'qwe'}", ZiQlParserError.MemberNotFound);
try expectParsingError("ADD User (name='Bob')", ZiQlParserError.MemberMissing);
try expectParsingError("GRAB User {name='Bob'", ZiQlParserError.SynthaxError);
try expectParsingError("GRAB User {age = 50 name='Bob'}", ZiQlParserError.SynthaxError);
try expectParsingError("GRAB User {age <14 AND (age>55}", ZiQlParserError.SynthaxError);
try expectParsingError("GRAB User {name < 'Hello'}", ZiQlParserError.ConditionError);
}
const DBEngine = @import("main.zig").DBEngine;
fn testParsing(source: [:0]const u8) !void {
const TEST_DATA_DIR = @import("config.zig").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: ZiQlParserError) !void {
const TEST_DATA_DIR = @import("config.zig").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.zig").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();
}