Working new ZiQL pqrser for basic ADD and GRAB !!!

This commit is contained in:
Adrien Bouvais 2024-10-08 00:18:25 +02:00
parent 9829cb24b0
commit 67fb49ded5
6 changed files with 126 additions and 213 deletions

View File

@ -1,13 +1,12 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const DataEngine = @import("fileEngine.zig").FileEngine;
const cliTokenizer = @import("tokenizers/cli.zig").Tokenizer; const cliTokenizer = @import("tokenizers/cli.zig").Tokenizer;
const cliToken = @import("tokenizers/cli.zig").Token; const cliToken = @import("tokenizers/cli.zig").Token;
const ziqlTokenizer = @import("tokenizers/ziql.zig").Tokenizer; const ziqlTokenizer = @import("tokenizers/ziql.zig").Tokenizer;
const ziqlToken = @import("tokenizers/ziql.zig").Token; const ziqlToken = @import("tokenizers/ziql.zig").Token;
const ziqlParser = @import("ziqlParser.zig").Parser; const ziqlParser = @import("ziqlParser.zig").Parser;
const stdout = std.io.getStdOut().writer();
pub fn main() !void { pub fn main() !void {
// TODO: Use an environment variable for the path of the DB // TODO: Use an environment variable for the path of the DB
checkAndCreateDirectories(); checkAndCreateDirectories();
@ -28,6 +27,7 @@ pub fn main() !void {
const line_buf = try allocator.alloc(u8, 1024 * 50); const line_buf = try allocator.alloc(u8, 1024 * 50);
defer allocator.free(line_buf); defer allocator.free(line_buf);
// TODO: Use a State to prevent first_token and second_token
while (true) { while (true) {
std.debug.print("> ", .{}); std.debug.print("> ", .{});
const line = try std.io.getStdIn().reader().readUntilDelimiterOrEof(line_buf, '\n'); const line = try std.io.getStdIn().reader().readUntilDelimiterOrEof(line_buf, '\n');
@ -47,7 +47,7 @@ pub fn main() !void {
defer allocator.free(null_term_query_str); defer allocator.free(null_term_query_str);
try runCommand(null_term_query_str); try runCommand(null_term_query_str);
}, },
.keyword_help => std.debug.print("The run command will take a ZiQL query between \" and run it. eg: run \"GRAB User\"\n"), .keyword_help => std.debug.print("The run command will take a ZiQL query between \" and run it. eg: run \"GRAB User\"\n", .{}),
else => std.debug.print("After command run, need a string of a query, eg: \"GRAB User\"\n", .{}), else => std.debug.print("After command run, need a string of a query, eg: \"GRAB User\"\n", .{}),
} }
}, },
@ -55,8 +55,20 @@ pub fn main() !void {
const second_token = cliToker.next(); const second_token = cliToker.next();
switch (second_token.tag) { switch (second_token.tag) {
.keyword_describe => try runCommand("__DESCRIBE__"), .keyword_describe => std.debug.print("{s}\n", .{ // TODO: Change that to use the SchemaEngine
.keyword_build => std.debug.print("Need to do the SchemaEngine tu update and migrate the schema"), \\User (
\\ name: str,
\\ email: str,
\\)
\\
\\Message (
\\ content: str,
\\)
}),
.keyword_build => { // Maybe rename that in init now that I dont build binary anymore
const data_engine = DataEngine.init(allocator, null);
try data_engine.initDataFolder();
},
.keyword_help => { .keyword_help => {
std.debug.print("{s}", .{ std.debug.print("{s}", .{
\\Here are all available options to use with the schema command: \\Here are all available options to use with the schema command:
@ -75,8 +87,7 @@ pub fn main() !void {
\\ \\
\\run To run a query. Args => query: str, the query to execute. \\run To run a query. Args => query: str, the query to execute.
\\schema Build a new engine and print current schema. \\schema Build a new engine and print current schema.
\\kill To stop the process without saving \\quit To stop the process without saving
\\save Save the database to the normal files.
\\dump Create a new folder with all data as copy. Args => foldername: str, the name of the folder. \\dump Create a new folder with all data as copy. Args => foldername: str, the name of the folder.
\\bump Replace current data with a previous dump. Args => foldername: str, the name of the folder. \\bump Replace current data with a previous dump. Args => foldername: str, the name of the folder.
\\ \\
@ -115,7 +126,12 @@ fn checkAndCreateDirectories() void {
else => @panic("Error other than path already exists when trying to create the DATA directory.\n"), else => @panic("Error other than path already exists when trying to create the DATA directory.\n"),
}; };
cwd.makeDir("ZipponDB/ENGINE") catch |err| switch (err) { cwd.makeDir("ZipponDB/BACKUP") catch |err| switch (err) {
error.PathAlreadyExists => {},
else => @panic("Error other than path already exists when trying to create the ENGINE directory.\n"),
};
cwd.makeDir("ZipponDB/LOG") catch |err| switch (err) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => @panic("Error other than path already exists when trying to create the ENGINE directory.\n"), else => @panic("Error other than path already exists when trying to create the ENGINE directory.\n"),
}; };

View File

@ -4,7 +4,6 @@ const schemaEngine = @import("schemaEngine.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const UUID = @import("types/uuid.zig").UUID; const UUID = @import("types/uuid.zig").UUID;
const DataType = @import("types/dataType.zig").DataType; const DataType = @import("types/dataType.zig").DataType;
const stdout = std.io.getStdOut().writer();
//TODO: Create a union class and chose between file and memory //TODO: Create a union class and chose between file and memory
@ -12,9 +11,9 @@ const stdout = std.io.getStdOut().writer();
/// Or even get stats, whatever. If it touch files, it's here /// Or even get stats, whatever. If it touch files, it's here
pub const FileEngine = struct { pub const FileEngine = struct {
allocator: Allocator, allocator: Allocator,
dir: std.fs.Dir, // The path to the DATA folder path_to_DATA_dir: []const u8, // The path to the DATA folder
max_file_size: usize = 1e+8, // 100mb max_file_size: usize = 1e+7, // 10mb
//
const DataEngineError = error{ const DataEngineError = error{
ErrorCreateDataFolder, ErrorCreateDataFolder,
ErrorCreateStructFolder, ErrorCreateStructFolder,
@ -23,17 +22,6 @@ pub const FileEngine = struct {
ErrorCreateDataFile, ErrorCreateDataFile,
}; };
/// Suported operation for the filter
/// TODO: Add more operation, like IN for array and LIKE for regex
const Operation = enum {
equal,
different,
superior,
superior_or_equal,
inferior,
inferior_or_equal,
};
const ComparisonValue = union { const ComparisonValue = union {
int: i64, int: i64,
float: f64, float: f64,
@ -48,12 +36,11 @@ pub const FileEngine = struct {
/// use to parse file. It take a struct name and member name to know what to parse. /// use to parse file. It take a struct name and member name to know what to parse.
/// An Operation from equal, different, superior, superior_or_equal, ... /// An Operation from equal, different, superior, superior_or_equal, ...
/// The DataType from int, float and str /// The DataType from int, float and str
/// TODO: Change the value to be the right type and not just a string all the time
pub const Condition = struct { pub const Condition = struct {
struct_name: []const u8, struct_name: []const u8,
member_name: []const u8 = undefined, member_name: []const u8 = undefined,
value: []const u8 = undefined, value: []const u8 = undefined,
operation: Operation = undefined, operation: enum { equal, different, superior, superior_or_equal, inferior, inferior_or_equal } = undefined, // Add more stuff like IN
data_type: DataType = undefined, data_type: DataType = undefined,
pub fn init(struct_name: []const u8) Condition { pub fn init(struct_name: []const u8) Condition {
@ -62,20 +49,15 @@ pub const FileEngine = struct {
}; };
pub fn init(allocator: Allocator, DATA_path: ?[]const u8) FileEngine { pub fn init(allocator: Allocator, DATA_path: ?[]const u8) FileEngine {
const path = DATA_path orelse "ZipponDB/DATA"; // I think use env variable for the path, idk, something better at least than just that 😕
const dir = std.fs.cwd().openDir(path, .{}) catch @panic("Error opening ZipponDB/DATA");
return FileEngine{ return FileEngine{
.allocator = allocator, .allocator = allocator,
.dir = dir, .path_to_DATA_dir = DATA_path orelse "ZipponDB/DATA",
}; };
} }
pub fn deinit(self: *FileEngine) void {
self.dir.close();
}
/// Take a condition and an array of UUID and fill the array with all UUID that match the condition /// Take a condition and an array of UUID and fill the array with all UUID that match the condition
pub fn getUUIDListUsingCondition(self: *FileEngine, condition: Condition, uuid_array: *std.ArrayList(UUID)) !void { pub fn getUUIDListUsingCondition(self: FileEngine, condition: Condition, uuid_array: *std.ArrayList(UUID)) !void {
var file_names = std.ArrayList([]const u8).init(self.allocator); var file_names = std.ArrayList([]const u8).init(self.allocator);
self.getFilesNames(condition.struct_name, condition.member_name, &file_names) catch @panic("Can't get list of files"); self.getFilesNames(condition.struct_name, condition.member_name, &file_names) catch @panic("Can't get list of files");
defer { defer {
@ -85,14 +67,11 @@ pub const FileEngine = struct {
file_names.deinit(); file_names.deinit();
} }
const sub_path = std.fmt.allocPrint( const sub_path = std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}/{s}", .{ self.path_to_DATA_dir, condition.struct_name, condition.member_name, file_names.items[0] }) catch @panic("Can't create sub_path for init a DataIterator");
self.allocator,
"{s}/{s}/{s}",
.{ condition.struct_name, condition.member_name, file_names.items[0] },
) catch @panic("Can't create sub_path for init a DataIterator");
defer self.allocator.free(sub_path); defer self.allocator.free(sub_path);
var file = self.dir.openFile(sub_path, .{}) catch @panic("Can't open first file to init a data iterator"); var file = std.fs.cwd().openFile(sub_path, .{}) catch @panic("Can't open first file to init a data iterator");
defer file.close();
var output: [1024 * 50]u8 = undefined; // Maybe need to increase that as it limit the size of a line in files var output: [1024 * 50]u8 = undefined; // Maybe need to increase that as it limit the size of a line in files
var output_fbs = std.io.fixedBufferStream(&output); var output_fbs = std.io.fixedBufferStream(&output);
@ -133,7 +112,8 @@ pub const FileEngine = struct {
if (file_index == file_names.items.len) break; if (file_index == file_names.items.len) break;
// TODO: Update the file and reader to be the next file of the list // FIXME: Update the file and reader to be the next file of the list
std.debug.print("End of stream\n", .{});
break; break;
}, // file read till the end }, // file read till the end
@ -151,7 +131,7 @@ pub const FileEngine = struct {
switch (condition.data_type) { switch (condition.data_type) {
.int => if (compare_value.int == dataParsing.parseInt(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), .int => if (compare_value.int == dataParsing.parseInt(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])),
.float => if (compare_value.float == dataParsing.parseFloat(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), .float => if (compare_value.float == dataParsing.parseFloat(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])),
.str => if (std.mem.eql(u8, compare_value.str, output_fbs.getWritten()[38 .. output_fbs.getWritten().len - 1])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), .str => if (std.mem.eql(u8, compare_value.str, output_fbs.getWritten()[37..output_fbs.getWritten().len])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])),
.bool => if (compare_value.bool_ == dataParsing.parseBool(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])), .bool => if (compare_value.bool_ == dataParsing.parseBool(output_fbs.getWritten()[37..])) try uuid_array.append(try UUID.parse(output_fbs.getWritten()[0..36])),
// TODO: Implement for array too // TODO: Implement for array too
else => {}, else => {},
@ -203,13 +183,15 @@ pub const FileEngine = struct {
} }
} }
// TODO: Test leak on that // TODO: Clean a bit the code
pub fn writeEntity(self: *FileEngine, struct_name: []const u8, data_map: std.StringHashMap([]const u8)) !void { // Do I need multiple files too ? I mean it duplicate UUID a lot, if it's just to save a name like 'Bob', storing a long UUID is overkill
// I could just use a tabular data format with separator using space
pub fn writeEntity(self: FileEngine, struct_name: []const u8, data_map: std.StringHashMap([]const u8)) !void {
const uuid_str = UUID.init().format_uuid(); const uuid_str = UUID.init().format_uuid();
defer stdout.print("Added new {s} successfully using UUID: {s}\n", .{ defer std.debug.print("Added new {s} successfully using UUID: {s}\n", .{
struct_name, struct_name,
uuid_str, uuid_str,
}) catch {}; });
const member_names = schemaEngine.structName2structMembers(struct_name); const member_names = schemaEngine.structName2structMembers(struct_name);
for (member_names) |member_name| { for (member_names) |member_name| {
@ -218,122 +200,48 @@ pub const FileEngine = struct {
if (potential_file_name_to_use) |file_name| { if (potential_file_name_to_use) |file_name| {
defer self.allocator.free(file_name); defer self.allocator.free(file_name);
const file_index = self.fileName2Index(file_name); const path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}/{s}", .{ self.path_to_DATA_dir, struct_name, member_name, file_name });
const path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}", .{
struct_name,
member_name,
file_name,
});
defer self.allocator.free(path); defer self.allocator.free(path);
var file = self.dir.openFile(path, .{ var file = std.fs.cwd().openFile(path, .{
.mode = .read_write, .mode = .read_write,
}) catch { }) catch {
try stdout.print("Error opening data file.", .{}); std.debug.print("Error opening data file.", .{});
return; return;
}; };
defer file.close(); defer file.close();
try file.seekFromEnd(0); try file.seekFromEnd(0);
try file.writer().print("{s} {s}\n", .{ uuid_str, data_map.get(member_name).? }); try file.writer().print("{s} {s}\n", .{ uuid_str, data_map.get(member_name).? });
const path_to_main = try std.fmt.allocPrint(self.allocator, "{s}/{s}/main.zippondata", .{
struct_name,
member_name,
});
defer self.allocator.free(path_to_main);
var file_main = self.dir.openFile(path_to_main, .{
.mode = .read_write,
}) catch {
try stdout.print("Error opening data file.", .{});
return;
};
defer file_main.close();
try self.addUUIDToMainFile(file_main, file_index + 1, &uuid_str);
} else { } else {
const max_index = try self.maxFileIndex(struct_name, member_name); const max_index = try self.maxFileIndex(struct_name, member_name);
const new_file_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{d}.zippondata", .{ const new_file_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}/{d}.zippondata", .{ self.path_to_DATA_dir, struct_name, member_name, max_index + 1 });
struct_name,
member_name,
max_index + 1,
});
defer self.allocator.free(new_file_path); defer self.allocator.free(new_file_path);
try stdout.print("new file path: {s}\n", .{new_file_path}); std.debug.print("new file path: {s}\n", .{new_file_path});
const new_file = self.dir.createFile(new_file_path, .{}) catch @panic("Error creating new data file"); const new_file = std.fs.cwd().createFile(new_file_path, .{}) catch @panic("Error creating new data file");
defer new_file.close(); defer new_file.close();
try new_file.writer().print("{s} {s}\n", .{ &uuid_str, data_map.get(member_name).? }); try new_file.writer().print("{s} {s}\n", .{ &uuid_str, data_map.get(member_name).? });
const path_to_main = try std.fmt.allocPrint(self.allocator, "ZipponDB/DATA/{s}/{s}/main.zippondata", .{
struct_name,
member_name,
});
defer self.allocator.free(path_to_main);
var file_main = self.dir.openFile(path_to_main, .{
.mode = .read_write,
}) catch {
try stdout.print("Error opening data file.", .{});
@panic("");
};
defer file_main.close();
try file_main.seekFromEnd(0);
try file_main.writeAll("\n ");
try file_main.seekTo(0);
try self.addUUIDToMainFile(file_main, max_index + 1, &uuid_str);
} }
} }
} }
/// Use a filename in the format 1.zippondata and return the 1 /// Use a filename in the format 1.zippondata and return the 1
fn fileName2Index(_: *FileEngine, file_name: []const u8) usize { fn fileName2Index(_: FileEngine, file_name: []const u8) usize {
var iter_file_name = std.mem.tokenize(u8, file_name, "."); var iter_file_name = std.mem.tokenize(u8, file_name, ".");
const num_str = iter_file_name.next().?; const num_str = iter_file_name.next().?;
const num: usize = std.fmt.parseInt(usize, num_str, 10) catch @panic("Couln't parse the int of a zippondata file."); const num: usize = std.fmt.parseInt(usize, num_str, 10) catch @panic("Couln't parse the int of a zippondata file.");
return num; return num;
} }
/// Add an UUID at a specific index of a file fn getFilesNames(self: FileEngine, struct_name: []const u8, member_name: []const u8, file_names: *std.ArrayList([]const u8)) !void {
/// Used when some data are deleted from previous zippondata files and are now bellow the file size limit const path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}", .{ self.path_to_DATA_dir, struct_name, member_name });
fn addUUIDToMainFile(_: *FileEngine, file: std.fs.File, index: usize, uuid_str: []const u8) !void { defer self.allocator.free(path);
var output: [1024 * 50]u8 = undefined; // Maybe need to increase that as it limit the size of a line in files
var output_fbs = std.io.fixedBufferStream(&output);
const writer = output_fbs.writer();
var reader = file.reader(); var member_dir = try std.fs.cwd().openDir(path, .{ .iterate = true });
var line_num: usize = 1;
while (true) {
output_fbs.reset();
reader.streamUntilDelimiter(writer, '\n', null) catch |err| switch (err) { // Maybe do a better error handeling. Because if an error happend here, data are already written in files but not in main
error.EndOfStream => {
output_fbs.reset(); // clear buffer before exit
break;
}, // file read till the end
else => break,
};
if (line_num == index) {
try file.seekBy(-1);
try file.writer().print("{s} ", .{uuid_str});
return;
}
line_num += 1;
}
}
fn getFilesNames(self: *FileEngine, struct_name: []const u8, member_name: []const u8, file_names: *std.ArrayList([]const u8)) !void {
const sub_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ struct_name, member_name });
defer self.allocator.free(sub_path);
var member_dir = try self.dir.openDir(sub_path, .{ .iterate = true });
defer member_dir.close(); defer member_dir.close();
var iter = member_dir.iterate(); var iter = member_dir.iterate();
@ -345,11 +253,11 @@ pub const FileEngine = struct {
/// Use the map of file stat to find the first file with under the bytes limit. /// Use the map of file stat to find the first file with under the bytes limit.
/// return the name of the file. If none is found, return null. /// return the name of the file. If none is found, return null.
fn getFirstUsableFile(self: *FileEngine, struct_name: []const u8, member_name: []const u8) !?[]const u8 { fn getFirstUsableFile(self: FileEngine, struct_name: []const u8, member_name: []const u8) !?[]const u8 {
const sub_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ struct_name, member_name }); const path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}", .{ self.path_to_DATA_dir, struct_name, member_name });
defer self.allocator.free(sub_path); defer self.allocator.free(path);
var member_dir = try self.dir.openDir(sub_path, .{ .iterate = true }); var member_dir = try std.fs.cwd().openDir(path, .{ .iterate = true });
defer member_dir.close(); defer member_dir.close();
var iter = member_dir.iterate(); var iter = member_dir.iterate();
@ -364,12 +272,11 @@ pub const FileEngine = struct {
/// Iter over all file and get the max name and return the value of it as usize /// Iter over all file and get the max name and return the value of it as usize
/// So for example if there is 1.zippondata and 2.zippondata it return 2. /// So for example if there is 1.zippondata and 2.zippondata it return 2.
fn maxFileIndex(self: *FileEngine, struct_name: []const u8, member_name: []const u8) !usize { fn maxFileIndex(self: FileEngine, struct_name: []const u8, member_name: []const u8) !usize {
const buffer = try self.allocator.alloc(u8, 1024); // Adjust the size as needed const path = try std.fmt.allocPrint(self.allocator, "{s}/{s}/{s}", .{ self.path_to_DATA_dir, struct_name, member_name });
defer self.allocator.free(buffer); defer self.allocator.free(path);
const sub_path = try std.fmt.bufPrint(buffer, "{s}/{s}", .{ struct_name, member_name }); const member_dir = try std.fs.cwd().openDir(path, .{ .iterate = true });
const member_dir = try self.dir.openDir(sub_path, .{ .iterate = true });
var count: usize = 0; var count: usize = 0;
var iter = member_dir.iterate(); var iter = member_dir.iterate();
@ -381,14 +288,16 @@ pub const FileEngine = struct {
} }
// TODO: Give the option to keep , dump or erase the data // TODO: Give the option to keep , dump or erase the data
pub fn initDataFolder(self: *FileEngine) !void { pub fn initDataFolder(self: FileEngine) !void {
var data_dir = try std.fs.cwd().openDir(self.path_to_DATA_dir, .{});
defer data_dir.close();
for (schemaEngine.struct_name_list) |struct_name| { for (schemaEngine.struct_name_list) |struct_name| {
self.dir.makeDir(struct_name) catch |err| switch (err) { data_dir.makeDir(struct_name) catch |err| switch (err) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return DataEngineError.ErrorCreateStructFolder, else => return DataEngineError.ErrorCreateStructFolder,
}; };
const struct_dir = try self.dir.openDir(struct_name, .{}); const struct_dir = try data_dir.openDir(struct_name, .{});
defer struct_dir.close();
const member_names = schemaEngine.structName2structMembers(struct_name); const member_names = schemaEngine.structName2structMembers(struct_name);
for (member_names) |member_name| { for (member_names) |member_name| {
@ -397,15 +306,7 @@ pub const FileEngine = struct {
else => return DataEngineError.ErrorCreateMemberFolder, else => return DataEngineError.ErrorCreateMemberFolder,
}; };
const member_dir = try struct_dir.openDir(member_name, .{}); const member_dir = try struct_dir.openDir(member_name, .{});
defer member_dir.close();
blk: {
const file = member_dir.createFile("main.zippondata", .{}) catch |err| switch (err) {
error.PathAlreadyExists => break :blk,
else => return DataEngineError.ErrorCreateMainFile,
};
try file.writeAll("\n");
}
_ = member_dir.createFile("0.zippondata", .{}) catch |err| switch (err) { _ = member_dir.createFile("0.zippondata", .{}) catch |err| switch (err) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return DataEngineError.ErrorCreateDataFile, else => return DataEngineError.ErrorCreateDataFile,
@ -425,3 +326,9 @@ test "Get list of UUID using condition" {
const condition = FileEngine.Condition{ .struct_name = "User", .member_name = "email", .value = "adrien@mail.com", .operation = .equal, .data_type = .str }; const condition = FileEngine.Condition{ .struct_name = "User", .member_name = "email", .value = "adrien@mail.com", .operation = .equal, .data_type = .str };
try data_engine.getUUIDListUsingCondition(condition, &uuid_array); try data_engine.getUUIDListUsingCondition(condition, &uuid_array);
} }
test "Open dir" {
const dir = std.fs.cwd();
const sub_dir = try dir.openDir("src/types", .{});
_ = sub_dir;
}

View File

@ -1,8 +0,0 @@
User (
name: str,
email: str,
)
Message (
content: str,
)

View File

@ -5,17 +5,17 @@
const std = @import("std"); const std = @import("std");
const DataType = @import("types/dataType.zig").DataType; const DataType = @import("types/dataType.zig").DataType;
const struct_name_list: [2][]const u8 = .{ pub const struct_name_list: [2][]const u8 = .{
"User", "User",
"Message", "Message",
}; };
const struct_member_list: [2][]const []const u8 = .{ pub const struct_member_list: [2][]const []const u8 = .{
&[_][]const u8{ "name", "email", "age", "scores", "friends" }, &[_][]const u8{ "name", "email", "age", "scores", "friends" },
&[_][]const u8{"content"}, &[_][]const u8{"content"},
}; };
const struct_type_list: [2][]const DataType = .{ pub const struct_type_list: [2][]const DataType = .{
&[_]DataType{ .str, .str, .int, .int_array, .bool_array }, &[_]DataType{ .str, .str, .int, .int_array, .bool_array },
&[_]DataType{.str}, &[_]DataType{.str},
}; };
@ -93,7 +93,7 @@ pub fn checkIfAllMemberInMap(struct_name: []const u8, map: *std.StringHashMap([]
var count: u16 = 0; var count: u16 = 0;
for (all_struct_member) |key| { for (all_struct_member) |key| {
if (map.contains(key)) count += 1; if (map.contains(key)) count += 1 else std.debug.print("Missing: {s}\n", .{key});
} }
return ((count == all_struct_member.len) and (count == map.count())); return ((count == all_struct_member.len) and (count == map.count()));

View File

@ -17,8 +17,6 @@ pub const Token = struct {
.{ "ADD", .keyword_add }, .{ "ADD", .keyword_add },
.{ "IN", .keyword_in }, .{ "IN", .keyword_in },
.{ "null", .keyword_null }, .{ "null", .keyword_null },
.{ "__DESCRIBE__", .keyword__describe__ },
.{ "__INIT__", .keyword__init__ },
.{ "true", .bool_literal_true }, .{ "true", .bool_literal_true },
.{ "false", .bool_literal_false }, .{ "false", .bool_literal_false },
.{ "AND", .keyword_and }, .{ "AND", .keyword_and },
@ -41,8 +39,6 @@ pub const Token = struct {
keyword_null, keyword_null,
keyword_and, keyword_and,
keyword_or, keyword_or,
keyword__describe__,
keyword__init__,
string_literal, string_literal,
int_literal, int_literal,

View File

@ -9,36 +9,33 @@ const Allocator = std.mem.Allocator;
pub const Parser = struct { pub const Parser = struct {
allocator: Allocator, allocator: Allocator,
toker: *Tokenizer,
state: State, state: State,
data_engine: *DataEngine, toker: *Tokenizer,
data_engine: DataEngine,
additional_data: AdditionalData, additional_data: AdditionalData,
struct_name: []const u8 = undefined, struct_name: []const u8 = undefined,
action: Action = undefined, action: enum { GRAB, ADD, UPDATE, DELETE } = undefined,
pub fn init(allocator: Allocator, toker: *Tokenizer) Parser { pub fn init(allocator: Allocator, toker: *Tokenizer) Parser {
var data_engine = DataEngine.init(allocator, null); // Do I need to init a DataEngine at each Parser, can't I put it in the CLI parser instead ?
const data_engine = DataEngine.init(allocator, null);
return Parser{ return Parser{
.allocator = allocator, .allocator = allocator,
.toker = toker, .toker = toker,
.state = State.start, .state = State.start,
.data_engine = &data_engine, .data_engine = data_engine,
.additional_data = AdditionalData.init(allocator), .additional_data = AdditionalData.init(allocator),
}; };
} }
pub fn deinit(self: *Parser) void { pub fn deinit(self: *Parser) void {
self.additional_data.deinit(); self.additional_data.deinit();
//self.allocator.free(self.struct_name);
//self.data_engine.deinit();
} }
const Action = enum { const Options = struct {
GRAB, members_for_ordering: std.ArrayList([]const u8), // The list in the right order of member name to use to order the result
ADD, sense_for_ordering: enum { ASC, DESC },
UPDATE,
DELETE,
}; };
const State = enum { const State = enum {
@ -123,29 +120,21 @@ pub const Parser = struct {
.start => { .start => {
switch (token.tag) { switch (token.tag) {
.keyword_grab => { .keyword_grab => {
self.action = Action.GRAB; self.action = .GRAB;
self.state = State.expect_struct_name; self.state = State.expect_struct_name;
}, },
.keyword_add => { .keyword_add => {
self.action = Action.ADD; self.action = .ADD;
self.state = State.expect_struct_name; self.state = State.expect_struct_name;
}, },
.keyword_update => { .keyword_update => {
self.action = Action.UPDATE; self.action = .UPDATE;
self.state = State.expect_struct_name; self.state = State.expect_struct_name;
}, },
.keyword_delete => { .keyword_delete => {
self.action = Action.DELETE; self.action = .DELETE;
self.state = State.expect_struct_name; self.state = State.expect_struct_name;
}, },
.keyword__describe__ => {
std.debug.print("{s}", .{@embedFile("schema.zipponschema")});
self.state = State.end;
},
.keyword__init__ => {
try self.data_engine.initDataFolder();
self.state = State.end;
},
else => { else => {
self.printError("Error: Expected action keyword. Available: GRAB ADD DELETE UPDATE", &token); self.printError("Error: Expected action keyword. Available: GRAB ADD DELETE UPDATE", &token);
self.state = State.end; self.state = State.end;
@ -176,7 +165,7 @@ pub const Parser = struct {
.filter_and_send => { .filter_and_send => {
var array = std.ArrayList(UUID).init(self.allocator); var array = std.ArrayList(UUID).init(self.allocator);
defer array.deinit(); defer array.deinit();
try self.parseFilter(&array); try self.parseFilter(&array, self.struct_name, true);
self.sendEntity(array.items); self.sendEntity(array.items);
self.state = State.end; self.state = State.end;
}, },
@ -192,10 +181,12 @@ pub const Parser = struct {
.parse_new_data_and_add_data => { .parse_new_data_and_add_data => {
switch (self.action) { switch (self.action) {
.ADD => { .ADD => {
const data_map = std.StringHashMap([]const u8).init(self.allocator); var data_map = std.StringHashMap([]const u8).init(self.allocator);
defer data_map.deinit(); defer data_map.deinit();
self.parseNewData(&data_map); self.parseNewData(&data_map);
if (!schemaEngine.checkIfAllMemberInMap(self.struct_name, data_map)) {}
// TODO: Print the list of missing
if (!schemaEngine.checkIfAllMemberInMap(self.struct_name, &data_map)) self.printError("Error: Missing member", &token);
try self.data_engine.writeEntity(self.struct_name, data_map); try self.data_engine.writeEntity(self.struct_name, data_map);
self.state = State.end; self.state = State.end;
}, },
@ -214,7 +205,13 @@ pub const Parser = struct {
fn sendEntity(self: *Parser, uuid_array: []UUID) void { fn sendEntity(self: *Parser, uuid_array: []UUID) void {
_ = self; _ = self;
std.debug.print("Number of uuid to send: {d}", .{uuid_array.len}); std.debug.print("Number of uuid to send: {d}\n", .{uuid_array.len});
}
// TODO: The parser that check what is between ||
// For now only |ASC name, age|
fn parseOptions(self: *Parser) void {
_ = self;
} }
/// Take an array of UUID and populate it to be the array that represent filter between {} /// Take an array of UUID and populate it to be the array that represent filter between {}
@ -234,9 +231,10 @@ pub const Parser = struct {
}) { }) {
switch (self.state) { switch (self.state) {
.expect_left_condition => { .expect_left_condition => {
self.parseCondition(&left_condition, &token); token = self.parseCondition(&left_condition, &token);
try self.data_engine.getUUIDListUsingCondition(left_condition, left_array); try self.data_engine.getUUIDListUsingCondition(left_condition, left_array);
self.state = State.expect_ANDOR_OR_end; self.state = State.expect_ANDOR_OR_end;
keep_next = true;
}, },
.expect_ANDOR_OR_end => { .expect_ANDOR_OR_end => {
switch (token.tag) { switch (token.tag) {
@ -274,7 +272,8 @@ pub const Parser = struct {
.identifier => { .identifier => {
var right_condition = Condition.init(struct_name); var right_condition = Condition.init(struct_name);
self.parseCondition(&right_condition, &token); token = self.parseCondition(&right_condition, &token);
keep_next = true;
try self.data_engine.getUUIDListUsingCondition(right_condition, &right_array); try self.data_engine.getUUIDListUsingCondition(right_condition, &right_array);
}, // Create a new condition and compare it }, // Create a new condition and compare it
else => self.printError("Error: Expecting ( or member name.", &token), else => self.printError("Error: Expecting ( or member name.", &token),
@ -288,6 +287,7 @@ pub const Parser = struct {
try OR(left_array, &right_array); try OR(left_array, &right_array);
}, },
} }
std.debug.print("Token here {any}\n", .{token});
self.state = .expect_ANDOR_OR_end; self.state = .expect_ANDOR_OR_end;
}, },
else => unreachable, else => unreachable,
@ -295,7 +295,7 @@ pub const Parser = struct {
} }
} }
fn parseCondition(self: *Parser, condition: *Condition, token_ptr: *Token) void { fn parseCondition(self: *Parser, condition: *Condition, token_ptr: *Token) Token {
var keep_next = false; var keep_next = false;
self.state = State.expect_member; self.state = State.expect_member;
var token = token_ptr.*; var token = token_ptr.*;
@ -406,6 +406,7 @@ pub const Parser = struct {
else => unreachable, else => unreachable,
} }
} }
return token;
} }
/// When this function is call, the tokenizer last token retrieved should be [. /// When this function is call, the tokenizer last token retrieved should be [.
@ -653,6 +654,7 @@ pub const Parser = struct {
} }
} }
// TODO: Stop panicking !
fn printError(self: *Parser, message: []const u8, token: *Token) void { fn printError(self: *Parser, message: []const u8, token: *Token) void {
std.debug.print("\n", .{}); std.debug.print("\n", .{});
std.debug.print("{s}\n", .{self.toker.buffer}); std.debug.print("{s}\n", .{self.toker.buffer});
@ -768,18 +770,6 @@ fn compareUUIDArray(arr1: std.ArrayList(UUID), arr2: std.ArrayList(UUID)) bool {
return true; return true;
} }
test "Parse filter" {
const allocator = std.testing.allocator;
var tokenizer = Tokenizer.init("{name = 'Adrien'}");
var parser = Parser.init(allocator, &tokenizer);
_ = tokenizer.next();
var uuid_array = std.ArrayList(UUID).init(allocator);
defer uuid_array.deinit();
try parser.parseFilter(&uuid_array, "User", true);
}
test "Parse condition" { test "Parse condition" {
const condition1 = Condition{ .data_type = .int, .member_name = "age", .operation = .superior_or_equal, .struct_name = "User", .value = "26" }; const condition1 = Condition{ .data_type = .int, .member_name = "age", .operation = .superior_or_equal, .struct_name = "User", .value = "26" };
try testConditionParsing("age >= 26", condition1); try testConditionParsing("age >= 26", condition1);
@ -798,7 +788,7 @@ fn testConditionParsing(source: [:0]const u8, expected_condition: Condition) !vo
var token = tokenizer.next(); var token = tokenizer.next();
var condition = Condition.init("User"); var condition = Condition.init("User");
parser.parseCondition(&condition, &token); _ = parser.parseCondition(&condition, &token);
try std.testing.expect(compareCondition(expected_condition, condition)); try std.testing.expect(compareCondition(expected_condition, condition));
} }
@ -807,8 +797,6 @@ fn compareCondition(c1: Condition, c2: Condition) bool {
return ((std.mem.eql(u8, c1.value, c2.value)) and (std.mem.eql(u8, c1.struct_name, c2.struct_name)) and (std.mem.eql(u8, c1.member_name, c2.member_name)) and (c1.operation == c2.operation) and (c1.data_type == c2.data_type)); return ((std.mem.eql(u8, c1.value, c2.value)) and (std.mem.eql(u8, c1.struct_name, c2.struct_name)) and (std.mem.eql(u8, c1.member_name, c2.member_name)) and (c1.operation == c2.operation) and (c1.data_type == c2.data_type));
} }
// TODO: Test Filter parser
test "Parse new data" { test "Parse new data" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
@ -864,6 +852,20 @@ fn testNewDataParsing(source: [:0]const u8, expected_member_map: std.StringHashM
if ((error_found) or (expected_total_count != found_count)) @panic("=("); if ((error_found) or (expected_total_count != found_count)) @panic("=(");
} }
test "Parse filter" {
const allocator = std.testing.allocator;
var tokenizer = Tokenizer.init("{name = 'Adrien'}");
var parser = Parser.init(allocator, &tokenizer);
defer parser.deinit();
_ = tokenizer.next(); // Start at name
var uuid_array = std.ArrayList(UUID).init(allocator);
defer uuid_array.deinit();
try parser.parseFilter(&uuid_array, "User", true);
}
test "Parse additional data" { test "Parse additional data" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;