From e45d8579a94ef538bac3a3691e98d0b782497c92 Mon Sep 17 00:00:00 2001 From: MrBounty Date: Sun, 13 Oct 2024 11:26:44 +0200 Subject: [PATCH] Added the db metrics, use and init command Now the DataEngine is deinit and init again everytime the user swap database using db use. If a db is not selected, it will display an error --- src/cli.zig | 178 ++++++++++++++++++++++++++++++++--------- src/fileEngine.zig | 66 +++++++++++++-- src/tokenizers/cli.zig | 8 ++ src/utils.zig | 4 +- src/ziqlParser.zig | 3 +- 5 files changed, 215 insertions(+), 44 deletions(-) diff --git a/src/cli.zig b/src/cli.zig index f11da17..26b1a48 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -6,9 +6,13 @@ const cliToken = @import("tokenizers/cli.zig").Token; const ziqlTokenizer = @import("tokenizers/ziql.zig").Tokenizer; const ziqlToken = @import("tokenizers/ziql.zig").Token; const ziqlParser = @import("ziqlParser.zig").Parser; +const utils = @import("utils.zig"); const stdout = std.io.getStdOut().writer(); +// TODO: Use some global var like that +const version = "0.0.9"; + fn send(comptime format: []const u8, args: anytype) void { stdout.print(format, args) catch |err| { std.log.err("Can't send: {any}", .{err}); @@ -23,6 +27,9 @@ const State = enum { expect_query, expect_schema_command, expect_path_to_schema, + expect_db_command, + expect_path_to_new_db, + expect_path_to_db, quit, end, }; @@ -37,11 +44,19 @@ pub fn main() !void { } } - // TODO: Use the path of an environment variable if one found, otherwise wait for the user to use the schema init - checkAndCreateDirectories(); - var file_engine = FileEngine.init(allocator, null); + const path_env_variable = utils.getEnvVariables(allocator, "ZIPPONDB_PATH"); + var file_engine: FileEngine = undefined; defer file_engine.deinit(); + if (path_env_variable) |path| { + std.debug.print("Environment variable found: {s}\n", .{path}); + file_engine = FileEngine.init(allocator, path_env_variable.?); + defer allocator.free(path_env_variable.?); + } else { + file_engine = FileEngine.init(allocator, ""); + std.debug.print("No ZIPONDB_PATH envirionment variable found, please use the command:\n db use path/to/db \nor\n db new /path/to/dir\n", .{}); + } + const line_buf = try allocator.alloc(u8, 1024 * 50); defer allocator.free(line_buf); @@ -53,8 +68,6 @@ pub fn main() !void { const line = try std.io.getStdIn().reader().readUntilDelimiterOrEof(line_buf, '\n'); if (line) |line_str| { - const time_initial = std.time.microTimestamp(); - const null_term_line_str = try allocator.dupeZ(u8, line_str[0..line_str.len]); defer allocator.free(null_term_line_str); @@ -65,17 +78,34 @@ pub fn main() !void { while ((state != .end) and (state != .quit)) : (token = cliToker.next()) { switch (state) { .expect_main_command => switch (token.tag) { - .keyword_run => state = .expect_query, - .keyword_schema => state = .expect_schema_command, + .keyword_run => { + if (!file_engine.usable) { + send("Error: No database selected. Please use db new or db use.", .{}); + state = .end; + continue; + } + state = .expect_query; + }, + .keyword_db => state = .expect_db_command, + .keyword_schema => { + if (!file_engine.usable) { + send("Error: No database selected. Please use db new or db use.", .{}); + state = .end; + continue; + } + state = .expect_schema_command; + }, .keyword_help => { send("{s}", .{ - \\Welcome to ZipponDB! + \\Welcome to ZipponDB v0.1! \\ - \\run To run a query. Args => query: str, the query to execute. - \\schema Build a new engine and print current schema. - \\quit To stop the process without saving - \\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. + \\Available commands: + \\run To run a query. + \\db Create or chose a database. + \\schema Initialize the database schema. + \\quit Stop the CLI with memory safety. + \\ + \\ For more informations: https://github.com/MrBounty/ZipponDB \\ }); state = .end; @@ -83,7 +113,73 @@ pub fn main() !void { .keyword_quit => state = .quit, .eof => state = .end, else => { - send("Command need to start with a keyword, including: run, schema, help and quit\n", .{}); + send("Command need to start with a keyword, including: run, db, schema, help and quit", .{}); + state = .end; + }, + }, + + .expect_db_command => switch (token.tag) { + .keyword_new => state = .expect_path_to_new_db, + .keyword_use => state = .expect_path_to_db, + .keyword_metrics => { + if (!file_engine.usable) { + send("Error: No database selected. Please use db new or db use.", .{}); + state = .end; + continue; + } + + var buffer = std.ArrayList(u8).init(allocator); + defer buffer.deinit(); + + try file_engine.writeDbMetrics(&buffer); + send("{s}", .{buffer.items}); + state = .end; + }, + .keyword_help => { + send("{s}", .{ + \\Available commands: + \\new Create a new database using a path to a sub folder. + \\use Select another ZipponDB folder to use as database. + \\metrics Print some metrics of the current database. + \\ + \\ For more informations: https://github.com/MrBounty/ZipponDB + \\ + }); + state = .end; + }, + else => { + send("Error: db commands available: new, metrics, swap & help", .{}); + state = .end; + }, + }, + + .expect_path_to_db => switch (token.tag) { + .identifier => { + file_engine.deinit(); + file_engine = FileEngine.init(allocator, cliToker.getTokenSlice(token)); + send("Successfully started using the database!", .{}); + state = .end; + }, + else => { + send("Error Expect a path to a ZipponDB folder.", .{}); + state = .end; + }, + }, + + .expect_path_to_new_db => switch (token.tag) { + .identifier => { + checkAndCreateDirectories(cliToker.getTokenSlice(token), allocator) catch |err| { + send("Error: Coulnt create database directories: {any}", .{err}); + state = .end; + continue; + }; + file_engine.deinit(); + file_engine = FileEngine.init(allocator, cliToker.getTokenSlice(token)); + send("Successfully initialized the database!", .{}); + state = .end; + }, + else => { + send("Error Expect a path to a folder.", .{}); state = .end; }, }, @@ -96,17 +192,19 @@ pub fn main() !void { state = .end; }, .keyword_help => { - send("The run command will take a ZiQL query between \" and run it. eg: run \"GRAB User\"\n", .{}); + send("The run command take a ZiQL query between \" and run it. eg: run \"GRAB User\"", .{}); state = .end; }, else => { - send("After command run, need a string of a query, eg: \"GRAB User\"\n", .{}); + send("Error: After command run, need a query, eg: \"GRAB User\"", .{}); state = .end; }, }, .expect_schema_command => switch (token.tag) { .keyword_describe => { + if (std.mem.eql(u8, file_engine.path_to_ZipponDB_dir, "")) send("Error: No database selected. Please use db bew or db use.", .{}); + if (file_engine.null_terminated_schema_buff.len == 0) { send("Need to init the schema first. Please use the schema init path/to/schema command to start.", .{}); } else { @@ -117,16 +215,17 @@ pub fn main() !void { .keyword_init => state = .expect_path_to_schema, .keyword_help => { send("{s}", .{ - \\Here are all available options to use with the schema command: + \\Available commands: + \\describe Print the schema use by the currently selected database. + \\init Take the path to a schema file and initialize the database. \\ - \\describe Print the schema use by the current engine. - \\build Build a new engine using a schema file. Args => filename: str, path of schema file to use. Default 'schema.zipponschema'. + \\ For more informations: https://github.com/MrBounty/ZipponDB \\ }); state = .end; }, else => { - std.debug.print("schema available options: describe, build & help\n", .{}); + send("Error: schema commands available: describe, init & help", .{}); state = .end; }, }, @@ -143,11 +242,11 @@ pub fn main() !void { state = .end; }, }; - send("Successfully initialized the database!", .{}); + send("Successfully initialized the database schema!", .{}); state = .end; }, else => { - send("Expected a path to a schema file after the schema init command.", .{}); + send("Error: Expect path to schema file.", .{}); state = .end; }, }, @@ -155,11 +254,6 @@ pub fn main() !void { .quit, .end => break, } } - - const time_final = std.time.microTimestamp(); - const duration = time_final - time_initial; - std.debug.print("Time: {d:.2}ms\n", .{@as(f64, @floatFromInt(duration)) / 1000.0}); - if (state == .quit) break; } } @@ -187,26 +281,38 @@ pub fn runQuery(null_term_query_str: [:0]const u8, file_engine: *FileEngine) voi } // TODO: Put that in the FileEngine -fn checkAndCreateDirectories() void { +fn checkAndCreateDirectories(sub_path: []const u8, allocator: Allocator) !void { + var path_buff = try std.fmt.allocPrint(allocator, "{s}/ZipponDB", .{sub_path}); + defer allocator.free(path_buff); + const cwd = std.fs.cwd(); - cwd.makeDir("ZipponDB") catch |err| switch (err) { + cwd.makeDir(path_buff) catch |err| switch (err) { error.PathAlreadyExists => {}, - else => @panic("Error other than path already exists when trying to create the ZipponDB directory.\n"), + else => return err, }; - cwd.makeDir("ZipponDB/DATA") catch |err| switch (err) { + allocator.free(path_buff); + path_buff = try std.fmt.allocPrint(allocator, "{s}/ZipponDB/DATA", .{sub_path}); + + cwd.makeDir(path_buff) catch |err| switch (err) { error.PathAlreadyExists => {}, - else => @panic("Error other than path already exists when trying to create the DATA directory.\n"), + else => return err, }; - cwd.makeDir("ZipponDB/BACKUP") catch |err| switch (err) { + allocator.free(path_buff); + path_buff = try std.fmt.allocPrint(allocator, "{s}/ZipponDB/BACKUP", .{sub_path}); + + cwd.makeDir(path_buff) catch |err| switch (err) { error.PathAlreadyExists => {}, - else => @panic("Error other than path already exists when trying to create the ENGINE directory.\n"), + else => return err, }; - cwd.makeDir("ZipponDB/LOG") catch |err| switch (err) { + allocator.free(path_buff); + path_buff = try std.fmt.allocPrint(allocator, "{s}/ZipponDB/LOG", .{sub_path}); + + cwd.makeDir(path_buff) catch |err| switch (err) { error.PathAlreadyExists => {}, - else => @panic("Error other than path already exists when trying to create the ENGINE directory.\n"), + else => return err, }; } diff --git a/src/fileEngine.zig b/src/fileEngine.zig index 9501694..55c9d03 100644 --- a/src/fileEngine.zig +++ b/src/fileEngine.zig @@ -16,13 +16,14 @@ const AdditionalData = @import("ziqlParser.zig").Parser.AdditionalData; /// Or even get stats, whatever. If it touch files, it's here pub const FileEngine = struct { allocator: Allocator, - path_to_ZipponDB_dir: []const u8, // The path to the DATA folder + usable: bool, + path_to_ZipponDB_dir: []const u8, // Make that into a list max_file_size: usize = 5e+4, // 50kb TODO: Change null_terminated_schema_buff: [:0]u8, struct_array: std.ArrayList(SchemaStruct), - pub fn init(allocator: Allocator, path: ?[]const u8) FileEngine { - const path_to_ZipponDB_dir = path orelse "ZipponDB"; + pub fn init(allocator: Allocator, path: []const u8) FileEngine { + const path_to_ZipponDB_dir = path; var schema_buf = allocator.alloc(u8, 1024 * 50) catch @panic("Cant allocate the schema buffer"); defer allocator.free(schema_buf); @@ -41,6 +42,7 @@ pub const FileEngine = struct { .path_to_ZipponDB_dir = path_to_ZipponDB_dir, .null_terminated_schema_buff = null_terminated_schema_buff, .struct_array = struct_array, + .usable = !std.mem.eql(u8, path, ""), }; } @@ -78,6 +80,10 @@ pub const FileEngine = struct { } }; + pub fn setPath(self: *FileEngine, path: []const u8) void { + self.path_to_ZipponDB_dir = path; + } + // TODO: A function that take a list of UUID and write into the buffer the message tot send // Like the other, write it line by line then if the UUID is found, you write the data // The output need to be in the JSON format, so change '' into "" @@ -756,12 +762,62 @@ pub const FileEngine = struct { var iter = member_dir.iterate(); while (try iter.next()) |entry| { - if (entry.kind != std.fs.Dir.Entry.Kind.file) continue; + if (entry.kind != .file) continue; count += 1; } return count - 1; } + pub fn writeDbMetrics(self: *FileEngine, buffer: *std.ArrayList(u8)) !void { + const path = try std.fmt.allocPrint(self.allocator, "{s}", .{self.path_to_ZipponDB_dir}); + defer self.allocator.free(path); + + const main_dir = try std.fs.cwd().openDir(path, .{ .iterate = true }); + + const writer = buffer.writer(); + try writer.print("Database path: {s}\n", .{path}); + const main_size = try self.getDirTotalSize(main_dir); + try writer.print("Total size: {d:.2}Mb\n", .{@as(f64, @floatFromInt(main_size)) / 1e-6}); + + const log_dir = try main_dir.openDir("LOG", .{ .iterate = true }); + const log_size = try self.getDirTotalSize(log_dir); + try writer.print("LOG: {d:.2}Mb\n", .{@as(f64, @floatFromInt(log_size)) / 1e-6}); + + const backup_dir = try main_dir.openDir("BACKUP", .{ .iterate = true }); + const backup_size = try self.getDirTotalSize(backup_dir); + try writer.print("BACKUP: {d:.2}Mb\n", .{@as(f64, @floatFromInt(backup_size)) / 1e-6}); + + const data_dir = try main_dir.openDir("DATA", .{ .iterate = true }); + const data_size = try self.getDirTotalSize(data_dir); + try writer.print("DATA: {d:.2}Mb\n", .{@as(f64, @floatFromInt(data_size)) / 1e-6}); + + var iter = data_dir.iterate(); + while (try iter.next()) |entry| { + if (entry.kind != .directory) continue; + const sub_dir = try data_dir.openDir(entry.name, .{ .iterate = true }); + const size = try self.getDirTotalSize(sub_dir); + try writer.print(" {s}: {d:.}Mb\n", .{ entry.name, @as(f64, @floatFromInt(size)) / 1e-6 }); + } + } + + // Maybe make it so it use itself to search if it find a directory + fn getDirTotalSize(self: FileEngine, dir: std.fs.Dir) !u64 { + var total: u64 = 0; + var stat: std.fs.File.Stat = undefined; + var iter = dir.iterate(); + while (try iter.next()) |entry| { + if (entry.kind == .directory) { + const sub_dir = try dir.openDir(entry.name, .{ .iterate = true }); + total += try self.getDirTotalSize(sub_dir); + } + + if (entry.kind != .file) continue; + stat = try dir.statFile(entry.name); + total += stat.size; + } + return total; + } + const FileError = error{ SchemaFileNotFound, SchemaNotConform, @@ -934,7 +990,7 @@ pub const FileEngine = struct { test "Get list of UUID using condition" { const allocator = std.testing.allocator; - var file_engine = FileEngine.init(allocator, null); + var file_engine = FileEngine.init(allocator, "ZipponDB"); defer file_engine.deinit(); var uuid_array = std.ArrayList(UUID).init(allocator); diff --git a/src/tokenizers/cli.zig b/src/tokenizers/cli.zig index 5f5dfc0..57ab64d 100644 --- a/src/tokenizers/cli.zig +++ b/src/tokenizers/cli.zig @@ -17,6 +17,10 @@ pub const Token = struct { .{ "init", .keyword_init }, .{ "schema", .keyword_schema }, .{ "quit", .keyword_quit }, + .{ "db", .keyword_db }, + .{ "new", .keyword_new }, + .{ "metrics", .keyword_metrics }, + .{ "use", .keyword_use }, }); pub fn getKeyword(bytes: []const u8) ?Tag { @@ -33,6 +37,10 @@ pub const Token = struct { keyword_schema, keyword_init, keyword_quit, + keyword_db, + keyword_new, + keyword_metrics, + keyword_use, string_literal, identifier, diff --git a/src/utils.zig b/src/utils.zig index bb81675..66c3009 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -1,13 +1,13 @@ const std = @import("std"); pub fn getEnvVariables(allocator: std.mem.Allocator, variable: []const u8) ?[]const u8 { - var env_map = try std.process.getEnvMap(allocator); + var env_map = std.process.getEnvMap(allocator) catch return null; defer env_map.deinit(); var iter = env_map.iterator(); while (iter.next()) |entry| { - if (std.mem.eql(u8, entry.key_ptr.*, variable)) return allocator.dupe(u8, entry.key_ptr.*); + if (std.mem.eql(u8, entry.key_ptr.*, variable)) return allocator.dupe(u8, entry.value_ptr.*) catch return null; } return null; diff --git a/src/ziqlParser.zig b/src/ziqlParser.zig index a3ed419..b924326 100644 --- a/src/ziqlParser.zig +++ b/src/ziqlParser.zig @@ -873,12 +873,13 @@ test "GRAB filter with int" { test "Specific query" { try testParsing("GRAB User"); try testParsing("GRAB User {}"); + try testParsing("GRAB User [1]"); } fn testParsing(source: [:0]const u8) !void { const allocator = std.testing.allocator; - var file_engine = FileEngine.init(allocator, null); + var file_engine = FileEngine.init(allocator, "ZipponDB"); defer file_engine.deinit(); var tokenizer = Tokenizer.init(source);