const std = @import("std"); const utils = @import("stuffs/utils.zig"); const send = utils.send; const Allocator = std.mem.Allocator; const Pool = std.Thread.Pool; const FileEngine = @import("fileEngine.zig").FileEngine; const SchemaEngine = @import("schemaEngine.zig").SchemaEngine; const ThreadEngine = @import("threadEngine.zig").ThreadEngine; const cliTokenizer = @import("tokenizers/cli.zig").Tokenizer; 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 ZipponError = @import("stuffs/errors.zig").ZipponError; const config = @import("config"); const BUFFER_SIZE = config.BUFFER_SIZE; const CPU_CORE = config.CPU_CORE; const HELP_MESSAGE = config.HELP_MESSAGE; const State = enum { expect_main_command, expect_query, expect_schema_command, expect_path_to_schema, expect_db_command, expect_path_to_db, quit, end, }; // End up using like 302kB of memory here var log_buff: [1024]u8 = undefined; var log_path: []const u8 = undefined; var date_buffer: [64]u8 = undefined; var date_fa = std.heap.FixedBufferAllocator.init(&date_buffer); const date_allocator = date_fa.allocator(); var path_buffer: [1024]u8 = undefined; var line_buffer: [BUFFER_SIZE]u8 = undefined; var in_buffer: [BUFFER_SIZE]u8 = undefined; var out_buffer: [BUFFER_SIZE]u8 = undefined; const log = std.log.scoped(.cli); pub const std_options = .{ .logFn = myLog, }; pub fn myLog( comptime message_level: std.log.Level, comptime scope: @Type(.EnumLiteral), comptime format: []const u8, args: anytype, ) void { if (true) return; const level_txt = comptime message_level.asText(); const prefix = if (scope == .default) " - " else "(" ++ @tagName(scope) ++ ") - "; const potential_file: ?std.fs.File = std.fs.cwd().openFile(log_path, .{ .mode = .write_only }) catch null; date_fa.reset(); const now = @import("dtype").DateTime.now(); var date_format_buffer = std.ArrayList(u8).init(date_allocator); defer date_format_buffer.deinit(); now.format("YYYY/MM/DD-HH:mm:ss.SSSS", date_format_buffer.writer()) catch return; if (potential_file) |file| { file.seekFromEnd(0) catch return; const writer = file.writer(); writer.print("{s}{s}Time: {s} - ", .{ level_txt, prefix, date_format_buffer.items }) catch return; writer.print(format, args) catch return; writer.writeByte('\n') catch return; file.close(); } else { //const writer = std.io.getStdErr().writer(); //writer.print("{s}{s}Time: {s} - ", .{ level_txt, prefix, date_format_buffer.items }) catch return; //writer.print(format, args) catch return; //writer.writeByte('\n') catch return; } } const DBEngineState = enum { MissingFileEngine, MissingSchemaEngine, Ok, Init }; pub const DBEngine = struct { state: DBEngineState = .Init, file_engine: FileEngine = undefined, schema_engine: SchemaEngine = undefined, thread_engine: ThreadEngine = undefined, pub fn init(potential_main_path: ?[]const u8, potential_schema_path: ?[]const u8) DBEngine { var self = DBEngine{}; self.thread_engine = ThreadEngine.init(); const potential_main_path_or_environment_variable = potential_main_path orelse utils.getEnvVariable("ZIPPONDB_PATH"); if (potential_main_path_or_environment_variable) |main_path| { log_path = std.fmt.bufPrint(&log_buff, "{s}/LOG/log", .{main_path}) catch ""; log.info("Found ZIPPONDB_PATH: {s}.", .{main_path}); self.file_engine = FileEngine.init(main_path, self.thread_engine.thread_pool) catch { log.err("Error when init FileEngine", .{}); self.state = .MissingFileEngine; return self; }; self.file_engine.createMainDirectories() catch { log.err("Error when creating main directories", .{}); self.state = .MissingFileEngine; return self; }; self.state = .MissingSchemaEngine; } else { log.info("No ZIPPONDB_PATH found.", .{}); self.state = .MissingFileEngine; return self; } if (self.file_engine.isSchemaFileInDir() and potential_schema_path == null) { const schema_path = std.fmt.bufPrint(&path_buffer, "{s}/schema", .{self.file_engine.path_to_ZipponDB_dir}) catch { self.state = .MissingSchemaEngine; return self; }; log.info("Schema founded in the database directory.", .{}); self.schema_engine = SchemaEngine.init(schema_path, &self.file_engine) catch |err| { log.err("Error when init SchemaEngine: {any}", .{err}); self.state = .MissingSchemaEngine; return self; }; self.file_engine.createStructDirectories(self.schema_engine.struct_array) catch |err| { log.err("Error when creating struct directories: {any}", .{err}); self.state = .MissingSchemaEngine; return self; }; log.debug("SchemaEngine created in DBEngine with {d} struct", .{self.schema_engine.struct_array.len}); self.file_engine.schema_engine = self.schema_engine; self.state = .Ok; return self; } log.info("Database don't have any schema yet, trying to add one.", .{}); const potential_schema_path_or_environment_variable = potential_schema_path orelse utils.getEnvVariable("ZIPPONDB_SCHEMA"); if (potential_schema_path_or_environment_variable) |schema_path| { log.info("Found schema path {s}.", .{schema_path}); self.schema_engine = SchemaEngine.init(schema_path, &self.file_engine) catch |err| { log.err("Error when init SchemaEngine: {any}", .{err}); self.state = .MissingSchemaEngine; return self; }; self.file_engine.createStructDirectories(self.schema_engine.struct_array) catch |err| { log.err("Error when creating struct directories: {any}", .{err}); self.state = .MissingSchemaEngine; return self; }; self.file_engine.schema_engine = self.schema_engine; self.file_engine.writeSchemaFile(self.schema_engine.null_terminated) catch |err| { log.err("Error saving schema file: {any}", .{err}); self.state = .MissingSchemaEngine; return self; }; self.state = .Ok; } else { log.info(HELP_MESSAGE.no_schema, .{self.file_engine.path_to_ZipponDB_dir}); } return self; } pub fn runQuery(self: *DBEngine, null_term_query_str: [:0]const u8) void { var toker = ziqlTokenizer.init(null_term_query_str); var parser = ziqlParser.init(&toker, &self.file_engine, &self.schema_engine); parser.parse() catch |err| log.err("Error parsing: {any}", .{err}); } pub fn deinit(self: *DBEngine) void { self.thread_engine.deinit(); self.schema_engine.deinit(); } }; pub fn main() !void { var db_engine = DBEngine.init(null, null); defer db_engine.deinit(); var fa = std.heap.FixedBufferAllocator.init(&out_buffer); const allocator = fa.allocator(); while (true) { fa.reset(); std.debug.print("> ", .{}); // TODO: Find something better than just std.debug.print const line = std.io.getStdIn().reader().readUntilDelimiterOrEof(&in_buffer, '\n') catch { log.debug("Command too long for buffer", .{}); continue; }; if (line) |line_str| { log.debug("Query received: {s}", .{line_str}); const null_term_line_str = try std.fmt.bufPrintZ(&line_buffer, "{s}", .{line_str}); var toker = cliTokenizer.init(null_term_line_str); var token = toker.next(); var state = State.expect_main_command; while ((state != .end) and (state != .quit)) : (token = toker.next()) switch (state) { .expect_main_command => switch (token.tag) { .keyword_run => { if (db_engine.state == .MissingFileEngine) { send("{s}", .{HELP_MESSAGE.no_engine}); state = .end; continue; } if (db_engine.state == .MissingSchemaEngine) { send(HELP_MESSAGE.no_schema, .{db_engine.file_engine.path_to_ZipponDB_dir}); state = .end; continue; } state = .expect_query; }, .keyword_db => state = .expect_db_command, .keyword_schema => { if (db_engine.state == .MissingFileEngine) { send("{s}", .{HELP_MESSAGE.no_engine}); state = .end; continue; } state = .expect_schema_command; }, .keyword_help => { send("{s}", .{HELP_MESSAGE.main}); state = .end; }, .keyword_quit => state = .quit, .eof => state = .end, else => { 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, .keyword_use => state = .expect_path_to_db, //TODO: When new, create the dir. If use, dont create the dir .keyword_metrics => { if (db_engine.state == .MissingFileEngine) { send("{s}", .{HELP_MESSAGE.no_engine}); state = .end; continue; } if (db_engine.state == .MissingSchemaEngine) { send(HELP_MESSAGE.no_schema, .{db_engine.file_engine.path_to_ZipponDB_dir}); state = .end; continue; } var buffer = std.ArrayList(u8).init(allocator); defer buffer.deinit(); try db_engine.file_engine.writeDbMetrics(&buffer); send("{s}", .{buffer.items}); state = .end; }, .keyword_state => { send("{any}", .{db_engine.state}); state = .end; }, .keyword_help => { send("{s}", .{HELP_MESSAGE.db}); state = .end; }, else => { send("Error: db commands available: new, metrics, swap & help", .{}); state = .end; }, }, .expect_path_to_db => switch (token.tag) { .identifier => { db_engine.deinit(); db_engine = DBEngine.init(toker.getTokenSlice(token), null); state = .end; }, else => { send("Error Expect a path to a ZipponDB folder.", .{}); state = .end; }, }, .expect_query => switch (token.tag) { .string_literal => { const null_term_query_str = try allocator.dupeZ(u8, toker.buffer[token.loc.start + 1 .. token.loc.end - 1]); defer allocator.free(null_term_query_str); db_engine.runQuery(null_term_query_str); // TODO: THis should return something and I should send from here, not from the parser state = .end; }, .keyword_help => { send("The run command take a ZiQL query between \" and run it. eg: run \"GRAB User\"", .{}); state = .end; }, else => { send("Error: After command run, need a query, eg: \"GRAB User\"", .{}); state = .end; }, }, .expect_schema_command => switch (token.tag) { .keyword_describe => { if (db_engine.state == .MissingFileEngine) send("Error: No database selected. Please use 'db new' or 'db use'.", .{}); if (db_engine.state == .MissingSchemaEngine) send("Error: No schema in database. Please use 'schema init'.", .{}); send("Schema:\n {s}", .{db_engine.schema_engine.null_terminated}); state = .end; }, .keyword_init => { if (db_engine.state == .MissingFileEngine) send("Error: No database selected. Please use 'db new' or 'db use'.", .{}); state = .expect_path_to_schema; }, .keyword_help => { send("{s}", .{HELP_MESSAGE.schema}); state = .end; }, else => { send("{s}", .{HELP_MESSAGE.schema}); state = .end; }, }, .expect_path_to_schema => switch (token.tag) { .identifier => { const main_path = try allocator.dupe(u8, db_engine.file_engine.path_to_ZipponDB_dir); db_engine.deinit(); db_engine = DBEngine.init(main_path, toker.getTokenSlice(token)); try db_engine.file_engine.writeSchemaFile(db_engine.schema_engine.null_terminated); state = .end; }, else => { send("Error: Expect path to schema file.", .{}); state = .end; }, }, .quit, .end => unreachable, }; if (state == .quit) { log.info("Bye bye\n", .{}); break; } } } }