344 lines
13 KiB
Zig
344 lines
13 KiB
Zig
const std = @import("std");
|
|
const utils = @import("stuffs/utils.zig");
|
|
const send = utils.send;
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const FileEngine = @import("fileEngine.zig").FileEngine;
|
|
|
|
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 BUFFER_SIZE = @import("config.zig").BUFFER_SIZE;
|
|
const HELP_MESSAGE = @import("config.zig").HELP_MESSAGE;
|
|
|
|
const State = enum {
|
|
expect_main_command,
|
|
expect_query,
|
|
expect_schema_command,
|
|
expect_path_to_schema,
|
|
expect_db_command,
|
|
expect_path_to_new_db,
|
|
expect_path_to_db,
|
|
quit,
|
|
end,
|
|
};
|
|
|
|
const log_allocator = std.heap.page_allocator;
|
|
var log_buff: [1024]u8 = undefined;
|
|
var log_path: []const 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 {
|
|
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;
|
|
|
|
const now = @import("dtype").DateTime.now();
|
|
var date_format_buffer = std.ArrayList(u8).init(log_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;
|
|
}
|
|
}
|
|
|
|
// TODO: If an argument is given when starting the binary, it is the db path
|
|
pub fn main() !void {
|
|
errdefer log.warn("Main function ended with an error", .{});
|
|
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
const allocator = gpa.allocator();
|
|
defer switch (gpa.deinit()) {
|
|
.ok => {},
|
|
.leak => log.debug("We fucked it up bro...\n", .{}),
|
|
};
|
|
|
|
var file_engine = try initFileEngine.init(allocator, null);
|
|
defer file_engine.deinit();
|
|
|
|
const line_buf = try allocator.alloc(u8, BUFFER_SIZE);
|
|
defer allocator.free(line_buf);
|
|
|
|
while (true) {
|
|
std.debug.print("> ", .{}); // TODO: Find something better than just std.debug.print
|
|
const line = try std.io.getStdIn().reader().readUntilDelimiterOrEof(line_buf, '\n');
|
|
|
|
if (line) |line_str| {
|
|
log.debug("Query received: {s}", .{line_str});
|
|
|
|
const null_term_line_str = try allocator.dupeZ(u8, line_str[0..line_str.len]);
|
|
defer allocator.free(null_term_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 (!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}", .{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 => 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}", .{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 => {
|
|
file_engine.deinit();
|
|
file_engine = try initFileEngine.init(allocator, try allocator.dupe(u8, toker.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 => {
|
|
file_engine.deinit();
|
|
file_engine = FileEngine.init(allocator, try allocator.dupe(u8, toker.getTokenSlice(token)));
|
|
file_engine.checkAndCreateDirectories() catch |err| {
|
|
send("Error: Coulnt create database directories: {any}", .{err});
|
|
state = .end;
|
|
continue;
|
|
};
|
|
send("Successfully initialized the database!", .{});
|
|
state = .end;
|
|
},
|
|
else => {
|
|
send("Error Expect a path to a 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);
|
|
runQuery(null_term_query_str, &file_engine);
|
|
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 (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 {
|
|
send("Schema:\n {s}", .{file_engine.null_terminated_schema_buff});
|
|
}
|
|
state = .end;
|
|
},
|
|
.keyword_init => state = .expect_path_to_schema,
|
|
.keyword_help => {
|
|
send("{s}", .{HELP_MESSAGE.schema});
|
|
state = .end;
|
|
},
|
|
else => {
|
|
send("Error: schema commands available: describe, init & help", .{});
|
|
state = .end;
|
|
},
|
|
},
|
|
|
|
.expect_path_to_schema => switch (token.tag) {
|
|
.identifier => {
|
|
file_engine.initDataFolder(toker.getTokenSlice(token)) catch |err| switch (err) {
|
|
error.SchemaFileNotFound => {
|
|
send("Coulnt find the schema file at {s}", .{toker.getTokenSlice(token)});
|
|
state = .end;
|
|
},
|
|
else => {
|
|
send("Error initializing the schema", .{});
|
|
state = .end;
|
|
},
|
|
};
|
|
send("Successfully initialized the database schema!", .{});
|
|
state = .end;
|
|
},
|
|
else => {
|
|
send("Error: Expect path to schema file.", .{});
|
|
state = .end;
|
|
},
|
|
},
|
|
|
|
.quit, .end => unreachable,
|
|
};
|
|
|
|
if (state == .quit) {
|
|
log.info("Bye bye\n", .{});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn runQuery(null_term_query_str: [:0]const u8, file_engine: *FileEngine) void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
const allocator = gpa.allocator();
|
|
|
|
var toker = ziqlTokenizer.init(null_term_query_str);
|
|
|
|
var parser = ziqlParser.init(allocator, &toker, file_engine);
|
|
|
|
parser.parse() catch |err| {
|
|
log.err("Error parsing: {any}", .{err});
|
|
};
|
|
|
|
switch (gpa.deinit()) {
|
|
.ok => {},
|
|
.leak => std.log.debug("We fucked it up bro...\n", .{}),
|
|
}
|
|
}
|
|
|
|
/// Simple struct to manage the init of the FileEngine, mostly managing if env path is here and init the directories, ect
|
|
const initFileEngine = struct {
|
|
fn init(allocator: std.mem.Allocator, potential_path: ?[]const u8) !FileEngine {
|
|
if (potential_path) |p| {
|
|
log_path = try std.fmt.bufPrint(&log_buff, "{s}/LOG/log", .{p});
|
|
log.info("Start using database path: {s}.", .{p});
|
|
return try initWithPath(allocator, p);
|
|
}
|
|
|
|
const path = utils.getEnvVariable(allocator, "ZIPPONDB_PATH");
|
|
defer if (path) |p| allocator.free(p);
|
|
|
|
if (path) |p| {
|
|
log_path = try std.fmt.bufPrint(&log_buff, "{s}/LOG/log", .{p});
|
|
log.info("Found environment variable ZIPPONDB_PATH: {s}.", .{p});
|
|
return try initWithPath(allocator, p);
|
|
} else {
|
|
log.info("No environment variable ZIPPONDB_PATH found.", .{});
|
|
return FileEngine.init(allocator, "");
|
|
}
|
|
}
|
|
|
|
fn initWithPath(allocator: std.mem.Allocator, path: []const u8) !FileEngine {
|
|
try ensureDirectoryExists(path);
|
|
var file_engine = FileEngine.init(allocator, path);
|
|
try file_engine.checkAndCreateDirectories();
|
|
|
|
if (!file_engine.isSchemaFileInDir()) {
|
|
try initSchema(allocator, &file_engine);
|
|
} else {
|
|
log.info("Database has a schema.", .{});
|
|
}
|
|
|
|
return file_engine;
|
|
}
|
|
|
|
fn ensureDirectoryExists(path: []const u8) !void {
|
|
_ = std.fs.cwd().openDir(path, .{}) catch |err| {
|
|
if (err == error.FileNotFound) {
|
|
log.info("{s} directory not found, creating it", .{path});
|
|
try std.fs.cwd().makeDir(path);
|
|
return;
|
|
} else {
|
|
return err;
|
|
}
|
|
};
|
|
}
|
|
|
|
fn initSchema(allocator: std.mem.Allocator, file_engine: *FileEngine) !void {
|
|
log.debug("Database doesn't have any schema. Checking if ZIPPONDB_SCHEMA env variable exists.", .{});
|
|
const schema = utils.getEnvVariable(allocator, "ZIPPONDB_SCHEMA");
|
|
defer if (schema) |s| allocator.free(s);
|
|
|
|
if (schema) |s| {
|
|
log.debug("Found environment variable ZIPPONDB_SCHEMA: {s}.", .{s});
|
|
file_engine.initDataFolder(s) catch {
|
|
log.warn("Couldn't use {s} as schema.\n", .{s});
|
|
};
|
|
} else {
|
|
log.debug("No environment variable ZIPPONDB_SCHEMA found.", .{});
|
|
}
|
|
}
|
|
};
|