Moved benchmark and test to src. Implemented array without testing yet

This commit is contained in:
2025-01-25 19:11:01 +01:00
parent a476cef2df
commit 78e8b67b80
13 changed files with 554 additions and 547 deletions
+297
View File
@@ -0,0 +1,297 @@
const std = @import("std");
const dtype = @import("dtype");
const DBEngine = @import("cli/core.zig");
const ZipponError = @import("error").ZipponError;
const names = [_][]const u8{ "Alice", "Bob", "Charlie", "Dave", "Eve" };
const emails = [_][]const u8{ "alice@email.com", "bob@email.com", "charlie@email.com", "dave@email.com", "eve@email.com" };
const dates = [_][]const u8{ "2000/01/01", "1954/04/02", "1998/01/21", "1977/12/31" };
const times = [_][]const u8{ "12:04", "20:45:11", "03:11:13", "03:00:01.0152" };
const datetimes = [_][]const u8{ "2000/01/01-12:04", "1954/04/02-20:45:11", "1998/01/21-03:11:13", "1977/12/31-03:00:01.0153" };
const ages = [_][]const u8{ "15", "11", "53", "34", "96", "64" };
const address = [_][]const u8{ "01 rue Notre Dame", "11 street St Bob", "Route to Heaven bis", "Here", "Not here", "Maybe here" };
pub const std_options = std.Options{
.log_level = .info,
.logFn = myLog,
};
const NUMBER_OF_RUN: usize = 10;
var date_buffer: [64]u8 = undefined;
var date_fa = std.heap.FixedBufferAllocator.init(&date_buffer);
const date_allocator = date_fa.allocator();
pub fn myLog(
comptime message_level: std.log.Level,
comptime scope: @Type(.enum_literal),
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("benchmarkDB/LOG/log", .{ .mode = .write_only }) catch null;
if (potential_file) |file| {
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;
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();
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
defer {
const deinit_status = gpa.deinit();
switch (deinit_status) {
.leak => @panic("Oupsy"),
.ok => {},
}
}
try benchmark(gpa.allocator());
}
test "benchmark" {
const allocator = std.testing.allocator;
try benchmark(allocator);
}
// Maybe I can make it a test to use the testing alloc
pub fn benchmark(allocator: std.mem.Allocator) !void {
const to_test = [_]usize{ 5_000, 100_000, 1_000_000, 10_000_000 };
var line_buffer: [1024 * 1024]u8 = undefined;
for (to_test) |users_count| {
var db_engine = DBEngine.init(allocator, "benchmarkDB", "schema/benchmark");
defer db_engine.deinit();
// Empty db
{
var null_term_query_str = try std.fmt.bufPrintZ(&line_buffer, "DELETE User {{}}", .{});
db_engine.runQuery(null_term_query_str);
null_term_query_str = try std.fmt.bufPrintZ(&line_buffer, "DELETE Category {{}}", .{});
db_engine.runQuery(null_term_query_str);
null_term_query_str = try std.fmt.bufPrintZ(&line_buffer, "DELETE Item {{}}", .{});
db_engine.runQuery(null_term_query_str);
null_term_query_str = try std.fmt.bufPrintZ(&line_buffer, "DELETE Order {{}}", .{});
db_engine.runQuery(null_term_query_str);
}
// Populate with random dummy value
{
std.debug.print("\n=====================================\n\n", .{});
var prng = std.Random.DefaultPrng.init(0);
const rng = prng.random();
// Category
{
const null_term_query_str = try std.fmt.bufPrintZ(&line_buffer, "ADD Category (name = 'Book') ('Food') ('Toy') ('Other')", .{});
db_engine.runQuery(null_term_query_str);
}
// Item
{
// TODO: Cache some filter. Here I end up parse Category everytime
const null_term_query_str = try std.fmt.bufPrintZ(
&line_buffer, // I dont like 'category = {name='Book'}'. Maybe att a IS keyword ?
\\ADD Item
\\(name='Book1', price=12.45, category = {{name='Book'}})
\\(name='Book2', price=10.45, category = {{name='Book'}})
\\(name='Book3', price=12.45, category = {{name='Book'}})
\\(name='Book4', price=2.00, category = {{name='Book'}})
\\(name='Book5', price=59.99, category = {{name='Book'}})
\\(name='Book6', price=10.45, category = {{name='Book'}})
\\(name='Book7', price=10.45, category = {{name='Book'}})
\\
\\(name='Food1', price=1.45, category = {{name='Food'}})
\\(name='Food2', price=1.45, category = {{name='Food'}})
\\(name='Food3', price=1.45, category = {{name='Food'}})
\\(name='Food4', price=1.45, category = {{name='Food'}})
\\(name='Food6', price=1.45, category = {{name='Food'}})
\\(name='Food7', price=1.45, category = {{name='Food'}})
\\(name='Food8', price=1.45, category = {{name='Food'}})
\\
\\(name='Toy1', price=10.45, category = {{name='Toy'}})
\\(name='Toy2', price=4.45, category = {{name='Toy'}})
\\(name='Toy3', price=6.45, category = {{name='Toy'}})
\\(name='Toy4', price=1.45, category = {{name='Toy'}})
\\
\\(name='Other', price=0.99, category = {{name='Other'}})
,
.{},
);
db_engine.runQuery(null_term_query_str);
}
// User
{
std.debug.print("Populating with {d} users.\n", .{users_count});
const populate_start_time = std.time.nanoTimestamp();
var array = std.ArrayList(u8).init(allocator);
defer array.deinit();
var writer = array.writer();
try writer.print(
"ADD User (name = '{s}', email='{s}', age={s}, address='{s}')",
.{
names[rng.uintAtMost(usize, names.len - 1)],
emails[rng.uintAtMost(usize, emails.len - 1)],
ages[rng.uintAtMost(usize, ages.len - 1)],
address[rng.uintAtMost(usize, address.len - 1)],
},
);
for (0..users_count - 1) |_| {
try writer.print(
"('{s}', '{s}', {s}, '{s}')",
.{
names[rng.uintAtMost(usize, names.len - 1)],
emails[rng.uintAtMost(usize, emails.len - 1)],
ages[rng.uintAtMost(usize, ages.len - 1)],
address[rng.uintAtMost(usize, address.len - 1)],
},
);
}
const null_term_query_str = try std.fmt.allocPrintZ(allocator, "{s}", .{array.items});
defer allocator.free(null_term_query_str);
db_engine.runQuery(null_term_query_str);
const populate_end_time = std.time.nanoTimestamp();
const populate_duration = @as(f64, @floatFromInt(populate_end_time - populate_start_time)) / 1e9;
std.debug.print("Populate duration: {d:.6} seconds\n\n", .{populate_duration});
}
// Order
if (false) {
// Linked array not yet implemented and array manipulation not tested
const null_term_query_str = try std.fmt.bufPrintZ(
&line_buffer, // I dont like 'category = {name='Book'}'. Maybe att a IS keyword ?
\\ADD Order (from={{}}, at=NOW, items={{name IN ['Food1', 'Food2']}}, quantity=[5 22])
,
.{},
);
db_engine.runQuery(null_term_query_str);
}
var buffer = std.ArrayList(u8).init(allocator);
defer buffer.deinit();
try db_engine.file_engine.writeDbMetrics(&buffer);
std.debug.print("{s}\n", .{buffer.items});
std.debug.print("--------------------------------------\n\n", .{});
}
if (false) {
for (db_engine.schema_engine.struct_array) |sstruct| {
const mb: f64 = @as(f64, @floatFromInt(sstruct.uuid_file_index.arena.queryCapacity())) / 1024.0 / 1024.0;
std.debug.print("Sstruct: {s}\n", .{sstruct.name});
std.debug.print("Memory: {d:.2}Mb\n", .{mb});
std.debug.print("Count: {d}\n\n", .{sstruct.uuid_file_index.map.count()});
std.debug.print("--------------------------------------\n\n", .{});
}
}
// Run query
{
var read_time: f64 = 0;
var read_write_time: f64 = 0;
const queries = [_][]const u8{
"GRAB User {}",
"GRAB User {name='asd'}",
"GRAB User [1] {}",
"GRAB User [name] {}",
"GRAB User {name = 'Charlie'}",
"GRAB Category {}",
"GRAB Item {}",
"GRAB Order {}",
"GRAB Order [from, items, quantity, at] {}",
"DELETE User {}",
};
// Run benchmarks
for (queries, 0..) |query, j| {
var time_buff: [NUMBER_OF_RUN]f64 = undefined;
for (0..NUMBER_OF_RUN) |i| {
const start_time = std.time.nanoTimestamp();
// Execute the query here
const null_term_query_str = try std.fmt.bufPrintZ(&line_buffer, "{s}", .{query});
db_engine.runQuery(null_term_query_str);
const end_time = std.time.nanoTimestamp();
time_buff[i] = @as(f64, @floatFromInt(end_time - start_time)) / 1e6;
}
std.debug.print(
"Query: \t{s}\nTime: \t{d:>6.2} ± {d:<6.2}ms | Min {d:>8.2}ms | Max {d:>8.2}ms\n\n",
.{ query, mean(time_buff), std_dev(time_buff), min(time_buff), max(time_buff) },
);
if (j == 0) read_write_time = mean(time_buff);
if (j == 1) read_time = mean(time_buff);
}
std.debug.print(
"Read: \t{d:.0} Entity/second\t*Include small condition\n",
.{@as(f64, @floatFromInt(users_count)) / (read_time / 1000)},
);
std.debug.print(
"Write: \t{d:.0} Entity/second\n",
.{@as(f64, @floatFromInt(users_count)) / ((read_write_time - read_time) / 1000)},
);
}
}
std.debug.print("=====================================\n\n", .{});
}
fn min(array: [NUMBER_OF_RUN]f64) f64 {
var current_min: f64 = 999999999999;
for (array) |value| {
if (value < current_min) current_min = value;
}
return current_min;
}
fn max(array: [NUMBER_OF_RUN]f64) f64 {
var current_max: f64 = 0;
for (array) |value| {
if (value > current_max) current_max = value;
}
return current_max;
}
fn mean(array: [NUMBER_OF_RUN]f64) f64 {
var total: f64 = 0;
for (array) |value| {
total += value;
}
return total / @as(f64, @floatFromInt(NUMBER_OF_RUN));
}
fn variance(array: [NUMBER_OF_RUN]f64) f64 {
const m = mean(array);
var square_diff: f64 = 0;
for (array) |value| {
square_diff += (value - m) * (value - m);
}
return square_diff / @as(f64, @floatFromInt(NUMBER_OF_RUN));
}
fn std_dev(array: [NUMBER_OF_RUN]f64) f64 {
const vari = variance(array);
return @sqrt(vari);
}
+47 -18
View File
@@ -69,6 +69,18 @@ pub const ConditionValue = union(enum) {
link: *std.AutoHashMap(UUID, void),
link_array: *std.AutoHashMap(UUID, void),
pub fn init(dtype: DataType, value: []const u8) ConditionValue {
return switch (dtype) {
.int => ConditionValue.initInt(value),
.float => ConditionValue.initFloat(value),
.bool => ConditionValue.initBool(value),
.date => ConditionValue.initDate(value),
.time => ConditionValue.initTime(value),
.datetime => ConditionValue.initDateTime(value),
.str => ConditionValue.initStr(value[1 .. value.len - 1]),
};
}
pub fn initInt(value: []const u8) ConditionValue {
return ConditionValue{ .int = s2t.parseInt(value) };
}
@@ -102,32 +114,24 @@ pub const ConditionValue = union(enum) {
}
// Array
pub fn initArrayInt(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
return ConditionValue{ .int_array = s2t.parseArrayInt(allocator, value) catch return ZipponError.ParsingValueError };
pub fn initArrayInt(value: []const i32) ZipponError!ConditionValue {
return ConditionValue{ .int_array = value };
}
pub fn initArrayFloat(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
return ConditionValue{ .float_array = s2t.parseArrayFloat(allocator, value) catch return ZipponError.ParsingValueError };
pub fn initArrayFloat(value: []const f64) ZipponError!ConditionValue {
return ConditionValue{ .float_array = value };
}
pub fn initArrayStr(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
return ConditionValue{ .str_array = s2t.parseArrayStr(allocator, value) catch return ZipponError.ParsingValueError };
pub fn initArrayStr(value: []const []const u8) ZipponError!ConditionValue {
return ConditionValue{ .str_array = value };
}
pub fn initArrayBool(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
return ConditionValue{ .bool_array = s2t.parseArrayBool(allocator, value) catch return ZipponError.ParsingValueError };
pub fn initArrayBool(value: []const bool) ZipponError!ConditionValue {
return ConditionValue{ .bool_array = value };
}
pub fn initArrayDate(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
return ConditionValue{ .unix_array = s2t.parseArrayDateUnix(allocator, value) catch return ZipponError.ParsingValueError };
}
pub fn initArrayTime(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
return ConditionValue{ .unix_array = s2t.parseArrayTimeUnix(allocator, value) catch return ZipponError.ParsingValueError };
}
pub fn initArrayDateTime(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
return ConditionValue{ .unix_array = s2t.parseArrayDatetimeUnix(allocator, value) catch return ZipponError.ParsingValueError };
pub fn initArrayUnix(value: []const u64) ZipponError!ConditionValue {
return ConditionValue{ .unix_array = value };
}
pub fn initLink(value: *std.AutoHashMap(UUID, void)) ConditionValue {
@@ -308,13 +312,28 @@ pub const Filter = struct {
else => unreachable,
},
// TODO: Add stuff to compare array, now condition with value as int, operator as in and to compare as []int should exist
.in => switch (condition.data_type) {
.link => condition.value.link.contains(UUID{ .bytes = row_value.UUID }),
.int => in(i32, row_value.Int, condition.value.int_array),
.float => in(f64, row_value.Float, condition.value.float_array),
.str => inStr(row_value.Str, condition.value.str_array),
.bool => in(bool, row_value.Bool, condition.value.bool_array),
.date => in(u64, row_value.Unix, condition.value.unix_array),
.time => in(u64, row_value.Unix, condition.value.unix_array),
.datetime => in(u64, row_value.Unix, condition.value.unix_array),
else => unreachable,
},
.not_in => switch (condition.data_type) {
.link => !condition.value.link.contains(UUID{ .bytes = row_value.UUID }),
.int => !in(i32, row_value.Int, condition.value.int_array),
.float => !in(f64, row_value.Float, condition.value.float_array),
.str => !inStr(row_value.Str, condition.value.str_array),
.bool => !in(bool, row_value.Bool, condition.value.bool_array),
.date => !in(u64, row_value.Unix, condition.value.unix_array),
.time => !in(u64, row_value.Unix, condition.value.unix_array),
.datetime => !in(u64, row_value.Unix, condition.value.unix_array),
else => unreachable,
},
};
@@ -343,6 +362,16 @@ pub const Filter = struct {
.empty => std.debug.print("Empty", .{}),
}
}
fn in(comptime T: type, value: T, array: []const T) bool {
for (array) |v| if (v == value) return true;
return false;
}
fn inStr(value: []const u8, array: []const []const u8) bool {
for (array) |v| if (std.mem.eql(u8, v, value)) return true;
return false;
}
};
test "Evaluate" {
+1 -1
View File
@@ -8,7 +8,7 @@ const DataType = dtype.DataType;
const DateTime = dtype.DateTime;
const UUID = dtype.UUID;
// TODO: Move this from FileEngine
// TODO: Move this outside of FileEngine and make it faster
const ZipponError = @import("error").ZipponError;
-1
View File
@@ -61,7 +61,6 @@ pub fn parse(self: *Parser, allocator: Allocator, struct_array: *std.ArrayList(S
},
.eof => state = .end,
else => {
std.debug.print("{s}\n", .{self.toker.getTokenSlice(token)});
return printError(
"Error parsing schema: Expected a struct name",
ZipponError.SynthaxError,
+200
View File
@@ -0,0 +1,200 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Parser = @import("ziql/parser.zig");
const Tokenizer = @import("ziql/tokenizer.zig").Tokenizer;
const DBEngine = @import("cli/core.zig");
const ZipponError = @import("error").ZipponError;
const DB = struct {
path: []const u8,
schema: []const u8,
};
test "Synthax error" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try expectParsingError(db, "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)", ZipponError.SynthaxError);
try expectParsingError(db, "GRAB {}", ZipponError.StructNotFound);
try expectParsingError(db, "GRAB User {qwe = 'qwe'}", ZipponError.MemberNotFound);
try expectParsingError(db, "ADD User (name='Bob')", ZipponError.MemberMissing);
try expectParsingError(db, "GRAB User {name='Bob'", ZipponError.SynthaxError);
try expectParsingError(db, "GRAB User {age = 50 name='Bob'}", ZipponError.SynthaxError);
try expectParsingError(db, "GRAB User {age <14 AND (age>55}", ZipponError.SynthaxError);
try expectParsingError(db, "GRAB User {name < 'Hello'}", ZipponError.ConditionError);
}
test "Clear" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "DELETE User {}");
}
test "ADD" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "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(db, "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(db, "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(db, "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)");
try testParsing(db, "ADD User (name = 'Bob', email='bob@email.com', age=-55, scores=[ 1 ], best_friend={name='Bob'}, friends=none, bday=2000/01/01, a_time=12:04:54.8741, last_order=2000/01/01-12:45)");
try testParsing(db, "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(db, "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(db, "GRAB User {}");
}
test "ADD batch" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "ADD User (name = 'ewq', email='ewq@email.com', age=22, scores=[ ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45) (name = 'Roger', email='roger@email.com', age=10, scores=[ 1 11 111 123 562345 123451234 34623465234 12341234 ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45)");
try testParsing(db, "ADD User (name = 'qwe', email='qwe@email.com', age=57, scores=[ ], best_friend=none, friends=none, bday=2000/01/01, a_time=12:04, last_order=2000/01/01-12:45) ('Rodrigo', 'bob@email.com', 55, [ 1 ], {name = 'qwe'}, none, 2000/01/01, 12:04, 2000/01/01-12:45)");
try testParsing(db, "GRAB User [name, best_friend] {name = 'Rodrigo'}");
try testParsing(db, "GRAB User {}");
}
test "GRAB filter with string" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User {name = 'Bob'}");
try testParsing(db, "GRAB User {name != 'Brittany Rogers'}");
}
test "GRAB with additional data" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User [1] {age < 18}");
try testParsing(db, "GRAB User [id, name] {age < 18}");
try testParsing(db, "GRAB User [100; name, age] {age < 18}");
}
test "UPDATE" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "UPDATE User [1] {name = 'Bob'} TO (email='new@gmail.com')");
try testParsing(db, "GRAB User {}");
}
test "GRAB filter with int" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User {age = 18}");
try testParsing(db, "GRAB User {age > -18}");
try testParsing(db, "GRAB User {age < 18}");
try testParsing(db, "GRAB User {age <= 18}");
try testParsing(db, "GRAB User {age >= 18}");
try testParsing(db, "GRAB User {age != 18}");
}
test "GRAB filter with date" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User {bday > 2000/01/01}");
try testParsing(db, "GRAB User {a_time < 08:00}");
try testParsing(db, "GRAB User {last_order > 2000/01/01-12:45}");
}
test "Specific query" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User");
try testParsing(db, "GRAB User {}");
try testParsing(db, "GRAB User [1]");
}
test "UPDATE relationship" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "UPDATE User [1] {name='Bob'} TO (best_friend = {name='Boba'} )");
try testParsing(db, "GRAB User {}");
}
test "GRAB Relationship Filter" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User {best_friend IN {name = 'Bob'}}");
try testParsing(db, "GRAB User {best_friend IN {name = 'Boba'}}");
}
test "GRAB Relationship AdditionalData" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User [name, friends] {}");
try testParsing(db, "GRAB User [name, best_friend] {}");
}
test "GRAB Relationship Sub AdditionalData" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User [name, friends [name]] {}");
try testParsing(db, "GRAB User [name, best_friend [name, friends [age]]] {}");
}
test "GRAB Relationship AdditionalData Filtered" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User [2; name, best_friend] {name = 'Bob'}");
try testParsing(db, "GRAB User [2; name, best_friend] {best_friend IN {}}");
try testParsing(db, "GRAB User [2; name, best_friend] {best_friend !IN {}}");
}
test "GRAB name IN" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "GRAB User {name IN ['Bob' 'Bobinou']}");
}
test "GRAB Relationship dot" {
// DO I add this ? I'm not sure about this feature
const db = DB{ .path = "test1", .schema = "schema/test" };
// try testParsing(db, "GRAB User.best_friend {}");
// try testParsing(db, "GRAB User.best_friend.best_friend {}");
// try testParsing(db, "GRAB User.best_friend.posts {}");
// try testParsing(db, "GRAB User.best_friend.posts.comments {}");
try testParsing(db, "GRAB User [1] {}");
}
test "DELETE" {
const db = DB{ .path = "test1", .schema = "schema/test" };
try testParsing(db, "DELETE User {}");
}
test "3 struct base" {
const db = DB{ .path = "test2", .schema = "schema/test-3struct" };
try testParsing(db, "DELETE User {}");
try testParsing(db, "DELETE Post {}");
try testParsing(db, "ADD User (name = 'Bob', email='bob@email.com', age=55, friends=none, posts=none, comments=none, bday=2000/01/01)");
try testParsing(db, "ADD Post (text = 'Hello every body', at=NOW, from={}, comments=none)");
try testParsing(db, "ADD Post (text = 'Hello every body', at=NOW, from={}, comments=none)");
try testParsing(db, "GRAB Post [id, text, at, from [id, name]] {}");
}
test "3 struct both side" {
const db = DB{ .path = "test2", .schema = "schema/test-3struct" };
try testParsing(db, "DELETE User {}");
try testParsing(db, "DELETE Post {}");
try testParsing(db, "ADD User (name = 'Bob', email='bob@email.com', age=55, friends=none, posts=none, comments=none, bday=2000/01/01)");
try testParsing(db, "ADD Post (text = 'Hello every body', at=NOW, from=none, comments=none)");
//try testParsing(db, "ADD Post (text = 'Hello every body', at=NOW, from={}, comments=none) -> new_post -> UPDATE User {} TO (posts APPEND new_post)");
// try testParsing(db, "ADD Post (text = 'Hello every body', at=NOW, from={} APPEND TO posts, comments=none)"); Maybe I can use that to be like the above query
// ADD Post (text = 'Hello every body', at=NOW, from={} TO last_post, comments=none) And this for a single link
// try testParsing(db, "ADD Post (text = 'Hello every body', at=NOW, from={} APPEND TO [posts, last_post], comments=none)"); Can be an array to add it to multiple list
// last_post is replaced instead of append
try testParsing(db, "GRAB Post [id, text, at, from [id, name]] {}");
try testParsing(db, "GRAB User [id, name] {}");
}
fn testParsing(db: DB, source: [:0]const u8) !void {
const allocator = std.testing.allocator;
var db_engine = DBEngine.init(allocator, db.path, db.schema);
defer db_engine.deinit();
var parser = Parser.init(
&db_engine.file_engine,
&db_engine.schema_engine,
);
std.debug.print("Running: {s}\n", .{source});
try parser.parse(allocator, source);
std.debug.print("\n\n-------------------------------\n\n", .{});
}
fn expectParsingError(db: DB, source: [:0]const u8, err: ZipponError) !void {
const allocator = std.testing.allocator;
var db_engine = DBEngine.init(allocator, db.path, db.schema);
defer db_engine.deinit();
var parser = Parser.init(
&db_engine.file_engine,
&db_engine.schema_engine,
);
std.debug.print("Running: {s}\n", .{source});
try std.testing.expectError(err, parser.parse(allocator, source));
std.debug.print("\n\n-------------------------------\n\n", .{});
}
+310
View File
@@ -0,0 +1,310 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const BORDER = "=" ** 80;
const log = std.log.scoped(.cli);
pub const std_options = std.Options{
.logFn = myLog,
};
pub fn myLog(
comptime message_level: std.log.Level,
comptime scope: @Type(.enum_literal),
comptime format: []const u8,
args: anytype,
) void {
_ = message_level;
_ = scope;
if (true) {
std.debug.print(format, args);
std.debug.print("\n", .{});
}
}
// use in custom panic handler
var current_test: ?[]const u8 = null;
pub fn main() !void {
var mem: [8192]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&mem);
const allocator = fba.allocator();
const env = Env.init(allocator);
defer env.deinit(allocator);
var slowest = SlowTracker.init(allocator, 5);
defer slowest.deinit();
var pass: usize = 0;
var fail: usize = 0;
var skip: usize = 0;
var leak: usize = 0;
const printer = Printer.init();
printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line
for (builtin.test_functions) |t| {
if (isSetup(t)) {
t.func() catch |err| {
printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{ t.name, err });
return err;
};
}
}
for (builtin.test_functions) |t| {
if (isSetup(t) or isTeardown(t)) {
continue;
}
var status = Status.pass;
slowest.startTiming();
const is_unnamed_test = isUnnamed(t);
if (env.filter) |f| {
if (!is_unnamed_test and std.mem.indexOf(u8, t.name, f) == null) {
continue;
}
}
const friendly_name = blk: {
const name = t.name;
var it = std.mem.splitScalar(u8, name, '.');
while (it.next()) |value| {
if (std.mem.eql(u8, value, "test")) {
const rest = it.rest();
break :blk if (rest.len > 0) rest else name;
}
}
break :blk name;
};
current_test = friendly_name;
std.testing.allocator_instance = .{};
const result = t.func();
current_test = null;
const ns_taken = slowest.endTiming(friendly_name);
if (std.testing.allocator_instance.deinit() == .leak) {
leak += 1;
printer.status(.fail, "\n{s}\n\"{s}\" - Memory Leak\n{s}\n", .{ BORDER, friendly_name, BORDER });
}
if (result) |_| {
pass += 1;
} else |err| switch (err) {
error.SkipZigTest => {
skip += 1;
status = .skip;
},
else => {
status = .fail;
fail += 1;
printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{ BORDER, friendly_name, @errorName(err), BORDER });
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
if (env.fail_first) {
break;
}
},
}
if (env.verbose) {
const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0;
printer.status(status, "{s} ({d:.2}ms)\n", .{ friendly_name, ms });
} else {
printer.status(status, ".", .{});
}
}
for (builtin.test_functions) |t| {
if (isTeardown(t)) {
t.func() catch |err| {
printer.status(.fail, "\nteardown \"{s}\" failed: {}\n", .{ t.name, err });
return err;
};
}
}
const total_tests = pass + fail;
const status = if (fail == 0) Status.pass else Status.fail;
printer.status(status, "\n{d} of {d} test{s} passed\n", .{ pass, total_tests, if (total_tests != 1) "s" else "" });
if (skip > 0) {
printer.status(.skip, "{d} test{s} skipped\n", .{ skip, if (skip != 1) "s" else "" });
}
if (leak > 0) {
printer.status(.fail, "{d} test{s} leaked\n", .{ leak, if (leak != 1) "s" else "" });
}
printer.fmt("\n", .{});
try slowest.display(printer);
printer.fmt("\n", .{});
std.posix.exit(if (fail == 0) 0 else 1);
}
const Printer = struct {
out: std.fs.File.Writer,
fn init() Printer {
return .{
.out = std.io.getStdErr().writer(),
};
}
fn fmt(self: Printer, comptime format: []const u8, args: anytype) void {
std.fmt.format(self.out, format, args) catch unreachable;
}
fn status(self: Printer, s: Status, comptime format: []const u8, args: anytype) void {
const color = switch (s) {
.pass => "\x1b[32m",
.fail => "\x1b[31m",
.skip => "\x1b[33m",
else => "",
};
const out = self.out;
out.writeAll(color) catch @panic("writeAll failed?!");
std.fmt.format(out, format, args) catch @panic("std.fmt.format failed?!");
self.fmt("\x1b[0m", .{});
}
};
const Status = enum {
pass,
fail,
skip,
text,
};
const SlowTracker = struct {
const SlowestQueue = std.PriorityDequeue(TestInfo, void, compareTiming);
max: usize,
slowest: SlowestQueue,
timer: std.time.Timer,
fn init(allocator: Allocator, count: u32) SlowTracker {
const timer = std.time.Timer.start() catch @panic("failed to start timer");
var slowest = SlowestQueue.init(allocator, {});
slowest.ensureTotalCapacity(count) catch @panic("OOM");
return .{
.max = count,
.timer = timer,
.slowest = slowest,
};
}
const TestInfo = struct {
ns: u64,
name: []const u8,
};
fn deinit(self: SlowTracker) void {
self.slowest.deinit();
}
fn startTiming(self: *SlowTracker) void {
self.timer.reset();
}
fn endTiming(self: *SlowTracker, test_name: []const u8) u64 {
var timer = self.timer;
const ns = timer.lap();
var slowest = &self.slowest;
if (slowest.count() < self.max) {
// Capacity is fixed to the # of slow tests we want to track
// If we've tracked fewer tests than this capacity, than always add
slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
return ns;
}
{
// Optimization to avoid shifting the dequeue for the common case
// where the test isn't one of our slowest.
const fastest_of_the_slow = slowest.peekMin() orelse unreachable;
if (fastest_of_the_slow.ns > ns) {
// the test was faster than our fastest slow test, don't add
return ns;
}
}
// the previous fastest of our slow tests, has been pushed off.
_ = slowest.removeMin();
slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
return ns;
}
fn display(self: *SlowTracker, printer: Printer) !void {
var slowest = self.slowest;
const count = slowest.count();
printer.fmt("Slowest {d} test{s}: \n", .{ count, if (count != 1) "s" else "" });
while (slowest.removeMinOrNull()) |info| {
const ms = @as(f64, @floatFromInt(info.ns)) / 1_000_000.0;
printer.fmt(" {d:.2}ms\t{s}\n", .{ ms, info.name });
}
}
fn compareTiming(context: void, a: TestInfo, b: TestInfo) std.math.Order {
_ = context;
return std.math.order(a.ns, b.ns);
}
};
const Env = struct {
verbose: bool,
fail_first: bool,
filter: ?[]const u8,
fn init(allocator: Allocator) Env {
return .{
.verbose = readEnvBool(allocator, "TEST_VERBOSE", true),
.fail_first = readEnvBool(allocator, "TEST_FAIL_FIRST", false),
.filter = readEnv(allocator, "TEST_FILTER"),
};
}
fn deinit(self: Env, allocator: Allocator) void {
if (self.filter) |f| {
allocator.free(f);
}
}
fn readEnv(allocator: Allocator, key: []const u8) ?[]const u8 {
const v = std.process.getEnvVarOwned(allocator, key) catch |err| {
if (err == error.EnvironmentVariableNotFound) {
return null;
}
std.log.warn("failed to get env var {s} due to err {}", .{ key, err });
return null;
};
return v;
}
fn readEnvBool(allocator: Allocator, key: []const u8, deflt: bool) bool {
const value = readEnv(allocator, key) orelse return deflt;
defer allocator.free(value);
return std.ascii.eqlIgnoreCase(value, "true");
}
};
fn isUnnamed(t: std.builtin.TestFn) bool {
const marker = ".test_";
const test_name = t.name;
const index = std.mem.indexOf(u8, test_name, marker) orelse return false;
_ = std.fmt.parseInt(u32, test_name[index + marker.len ..], 10) catch return false;
return true;
}
fn isSetup(t: std.builtin.TestFn) bool {
return std.mem.endsWith(u8, t.name, "tests:beforeAll");
}
fn isTeardown(t: std.builtin.TestFn) bool {
return std.mem.endsWith(u8, t.name, "tests:afterAll");
}
-1
View File
@@ -67,7 +67,6 @@ pub usingnamespace @import("parts/newData.zig");
pub usingnamespace @import("parts/value.zig");
pub usingnamespace @import("parts/filter.zig");
pub usingnamespace @import("parts/additionalData.zig");
pub usingnamespace @import("utils.zig");
toker: *Tokenizer = undefined,
file_engine: *FileEngine,
+95 -1
View File
@@ -71,11 +71,21 @@ pub fn parseCondition(
.expect_operation => {
condition.operation = try self.parseComparisonOperator(token);
if (condition.operation == .in) condition.data_type = switch (condition.data_type) {
.int => .int_array,
.float => .float_array,
.str => .str_array,
.bool => .bool_array,
.date => .date_array,
.time => .time_array,
.datetime => .datetime_array,
else => condition.data_type,
};
log.debug("Condition operation {any}\n", .{condition.operation});
state = .expect_value;
},
.expect_value => {
log.debug("Parse condition value of member {s}", .{member_name});
condition.value = try self.parseConditionValue(allocator, struct_name, member_name, condition.data_type, &token);
state = .end;
},
@@ -87,3 +97,87 @@ pub fn parseCondition(
return condition;
}
pub fn checkConditionValidity(
self: Self,
condition: Condition,
token: Token,
) ZipponError!void {
switch (condition.operation) {
.equal => switch (condition.data_type) {
.int, .float, .str, .bool, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, str, bool, date, time, datetime can be compare with =",
ZipponError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.different => switch (condition.data_type) {
.int, .float, .str, .bool, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, str, bool, date, time, datetime can be compare with !=",
ZipponError.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 >=",
ZipponError.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 >",
ZipponError.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 <=",
ZipponError.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 <",
ZipponError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.in => switch (condition.data_type) {
.link, .int_array, .float_array, .str_array, .bool_array, .time_array, .date_array, .datetime_array => {},
else => unreachable,
},
.not_in => switch (condition.data_type) {
.link, .int_array, .float_array, .str_array, .bool_array, .time_array, .date_array, .datetime_array => {},
else => unreachable,
},
}
}
+312 -136
View File
@@ -1,5 +1,6 @@
const std = @import("std");
const dtype = @import("dtype");
const s2t = dtype.s2t;
const UUID = dtype.UUID;
const Allocator = std.mem.Allocator;
const Token = @import("../tokenizer.zig").Token;
@@ -12,9 +13,10 @@ const ZipponError = @import("error").ZipponError;
var buff: [1024]u8 = undefined;
var zero_map_buf: [1024 * 4]u8 = undefined;
var zero_map_buf: [200]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&zero_map_buf);
var zero_map = std.AutoHashMap(UUID, void).init(fba.allocator());
var empty_map = std.AutoHashMap(UUID, void).init(fba.allocator());
const Self = @import("../parser.zig");
@@ -23,148 +25,324 @@ pub fn initZeroMap() ZipponError!void {
}
/// To run just after a condition like = or > or >= to get the corresponding ConditionValue that you need to compare
pub fn parseConditionValue(self: Self, allocator: Allocator, struct_name: []const u8, member_name: []const u8, data_type: dtype.DataType, token: *Token) ZipponError!ConditionValue {
pub fn parseConditionValue(
self: Self,
allocator: Allocator,
struct_name: []const u8,
member_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,
.int_array => .int_literal,
.float_array => .float_literal,
.str_array => .string_literal,
.bool, .bool_array, .link, .link_array, .date, .time, .datetime, .date_array, .time_array, .datetime_array => null, // handle separately
if (data_type.is_array()) switch (token.tag) {
.l_bracket => token.* = self.toker.next(),
else => return printError(
"Error: expecting [ to start array.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
};
// Check if the all next tokens are the right one
if (expected_tag) |tag| {
if (data_type.is_array()) {
token.* = try self.checkTokensInArray(tag);
switch (data_type) {
.self => unreachable,
.int => if (token.tag != .int_literal) {
return printError(
"Error: Wrong type. Expected: int.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
} else {
if (token.tag != tag) {
const msg = std.fmt.bufPrint(&buff, "Error: Wrong type. Expected: {any}", .{tag}) catch return ZipponError.MemoryError;
return printError(
msg,
return ConditionValue.initInt(self.toker.buffer[start_index..token.loc.end]);
},
.float => if (token.tag != .float_literal) {
return printError(
"Error: Wrong type. Expected: float.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
} else {
return ConditionValue.initFloat(self.toker.buffer[start_index..token.loc.end]);
},
.str => if (token.tag != .string_literal) {
return printError(
"Error: Wrong type. Expected: string.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
} else {
return ConditionValue.initStr(self.toker.buffer[start_index..token.loc.end]);
},
.bool => if (token.tag != .bool_literal_true and token.tag != .bool_literal_false) {
return printError(
"Error: Wrong type. Expected: bool.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
} else {
return ConditionValue.initBool(self.toker.buffer[start_index..token.loc.end]);
},
.date => if (token.tag != .date_literal and token.tag != .keyword_now) {
return printError(
"Error: Wrong type. Expected: date.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
} else {
return ConditionValue.initDate(self.toker.buffer[start_index..token.loc.end]);
},
.time => if (token.tag != .time_literal and token.tag != .keyword_now) {
return printError(
"Error: Wrong type. Expected: time.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
} else {
return ConditionValue.initTime(self.toker.buffer[start_index..token.loc.end]);
},
.datetime => if (token.tag != .date_literal and token.tag != .datetime_literal and token.tag != .keyword_now) {
return printError(
"Error: Wrong type. Expected: datetime.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
} else {
return ConditionValue.initDateTime(self.toker.buffer[start_index..token.loc.end]);
},
.int_array => {
var array = std.ArrayList(i32).init(allocator);
errdefer array.deinit();
var first = true;
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (!first) {
if (token.tag != .comma) return printError(
"Error: Expected comma.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
token.* = self.toker.next();
} else first = false;
if (token.tag != .int_literal) return printError(
"Error: Wrong type. Expected int.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
array.append(s2t.parseInt(self.toker.getTokenSlice(token.*))) catch return ZipponError.MemoryError;
}
}
} else switch (data_type) {
.bool => if (token.tag != .bool_literal_true and token.tag != .bool_literal_false) {
return printError(
"Error: Expected bool",
ZipponError.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 ]",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
}
},
.date => if (token.tag != .date_literal and token.tag != .keyword_now) {
return printError(
"Error: Expected date",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
},
.date_array => {
token.* = self.toker.next();
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (token.tag != .date_literal and token.tag != .keyword_now) {
return printError(
"Error: Expected date",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
}
},
.time => if (token.tag != .time_literal and token.tag != .keyword_now) {
return printError(
"Error: Expected time",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
},
.time_array => {
token.* = self.toker.next();
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (token.tag != .time_literal and token.tag != .keyword_now) {
return printError(
"Error: Expected time",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
}
},
.datetime => if (token.tag != .datetime_literal and token.tag != .keyword_now) {
return printError(
"Error: Expected datetime",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
},
.datetime_array => {
token.* = self.toker.next();
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (token.tag != .datetime_literal and token.tag != .keyword_now) {
return printError(
"Error: Expected datetime",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
}
},
.link, .link_array => {},
else => unreachable,
}
// And finally create the ConditionValue
switch (data_type) {
.int => return ConditionValue.initInt(self.toker.buffer[start_index..token.loc.end]),
.float => return ConditionValue.initFloat(self.toker.buffer[start_index..token.loc.end]),
.str => return ConditionValue.initStr(self.toker.buffer[start_index + 1 .. token.loc.end - 1]),
.date => return ConditionValue.initDate(self.toker.buffer[start_index..token.loc.end]),
.time => return ConditionValue.initTime(self.toker.buffer[start_index..token.loc.end]),
.datetime => return ConditionValue.initDateTime(self.toker.buffer[start_index..token.loc.end]),
.bool => return ConditionValue.initBool(self.toker.buffer[start_index..token.loc.end]),
.int_array => return try ConditionValue.initArrayInt(allocator, self.toker.buffer[start_index..token.loc.end]),
.str_array => return try ConditionValue.initArrayStr(allocator, self.toker.buffer[start_index..token.loc.end]),
.bool_array => return try ConditionValue.initArrayBool(allocator, self.toker.buffer[start_index..token.loc.end]),
.float_array => return try ConditionValue.initArrayFloat(allocator, self.toker.buffer[start_index..token.loc.end]),
.date_array => return try ConditionValue.initArrayDate(allocator, self.toker.buffer[start_index..token.loc.end]),
.time_array => return try ConditionValue.initArrayTime(allocator, self.toker.buffer[start_index..token.loc.end]),
.datetime_array => return try ConditionValue.initArrayDateTime(allocator, self.toker.buffer[start_index..token.loc.end]),
return try ConditionValue.initArrayInt(array.toOwnedSlice() catch return ZipponError.MemoryError);
},
.float_array => {
var array = std.ArrayList(f64).init(allocator);
errdefer array.deinit();
var first = true;
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (!first) {
if (token.tag != .comma) return printError(
"Error: Expected comma.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
token.* = self.toker.next();
} else first = false;
if (token.tag != .float_literal) return printError(
"Error: Wrong type. Expected float.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
array.append(s2t.parseFloat(self.toker.getTokenSlice(token.*))) catch return ZipponError.MemoryError;
}
return try ConditionValue.initArrayFloat(array.toOwnedSlice() catch return ZipponError.MemoryError);
},
.bool_array => {
var array = std.ArrayList(bool).init(allocator);
errdefer array.deinit();
var first = true;
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (!first) {
if (token.tag != .comma) return printError(
"Error: Expected comma.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
token.* = self.toker.next();
} else first = false;
if (token.tag != .bool_literal_false and token.tag != .bool_literal_true) return printError(
"Error: Wrong type. Expected bool.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
array.append(s2t.parseBool(self.toker.getTokenSlice(token.*))) catch return ZipponError.MemoryError;
}
return try ConditionValue.initArrayBool(array.toOwnedSlice() catch return ZipponError.MemoryError);
},
.str_array => {
var array = std.ArrayList([]const u8).init(allocator);
errdefer array.deinit();
var first = true;
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (!first) {
if (token.tag != .comma) return printError(
"Error: Expected comma.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
token.* = self.toker.next();
} else first = false;
if (token.tag != .string_literal) return printError(
"Error: Wrong type. Expected str.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
array.append(self.toker.getTokenSlice(token.*)) catch return ZipponError.MemoryError;
}
return try ConditionValue.initArrayStr(array.toOwnedSlice() catch return ZipponError.MemoryError);
},
.date_array => {
var array = std.ArrayList(u64).init(allocator);
errdefer array.deinit();
var first = true;
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (!first) {
if (token.tag != .comma) return printError(
"Error: Expected comma.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
token.* = self.toker.next();
} else first = false;
if (token.tag != .date_literal and token.tag != .keyword_now) return printError(
"Error: Wrong type. Expected date.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
array.append(s2t.parseDate(self.toker.getTokenSlice(token.*)).toUnix()) catch return ZipponError.MemoryError;
}
return try ConditionValue.initArrayUnix(array.toOwnedSlice() catch return ZipponError.MemoryError);
},
.time_array => {
var array = std.ArrayList(u64).init(allocator);
errdefer array.deinit();
var first = true;
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (!first) {
if (token.tag != .comma) return printError(
"Error: Expected comma.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
token.* = self.toker.next();
} else first = false;
if (token.tag != .time_literal and token.tag != .keyword_now) return printError(
"Error: Wrong type. Expected time.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
array.append(s2t.parseTime(self.toker.getTokenSlice(token.*)).toUnix()) catch return ZipponError.MemoryError;
}
return try ConditionValue.initArrayUnix(array.toOwnedSlice() catch return ZipponError.MemoryError);
},
.datetime_array => {
var array = std.ArrayList(u64).init(allocator);
errdefer array.deinit();
var first = true;
while (token.tag != .r_bracket) : (token.* = self.toker.next()) {
if (!first) {
if (token.tag != .comma) return printError(
"Error: Expected comma.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
token.* = self.toker.next();
} else first = false;
if (token.tag != .datetime_literal and token.tag != .date_literal and token.tag != .keyword_now) return printError(
"Error: Wrong type. Expected datetime.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
array.append(s2t.parseDatetime(self.toker.getTokenSlice(token.*)).toUnix()) catch return ZipponError.MemoryError;
}
return try ConditionValue.initArrayUnix(array.toOwnedSlice() catch return ZipponError.MemoryError);
},
.link => switch (token.tag) {
.keyword_none => {
_ = self.toker.next();
@@ -239,12 +417,11 @@ pub fn parseConditionValue(self: Self, allocator: Allocator, struct_name: []cons
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);
_ = self.toker.next();
return ConditionValue.initArrayLink(map);
return ConditionValue.initArrayLink(&empty_map);
},
.l_brace, .l_bracket => {
var filter: ?Filter = null;
@@ -292,6 +469,5 @@ pub fn parseConditionValue(self: Self, allocator: Allocator, struct_name: []cons
token.loc.end,
),
},
.self => unreachable,
}
}
-122
View File
@@ -1,122 +0,0 @@
const std = @import("std");
const config = @import("config");
const Allocator = std.mem.Allocator;
const Token = @import("tokenizer.zig").Token;
const Condition = @import("../dataStructure/filter.zig").Condition;
const printError = @import("../utils.zig").printError;
const ZipponError = @import("error").ZipponError;
const Self = @import("parser.zig");
/// Check if all token in an array is of one specific type
pub fn checkTokensInArray(self: Self, 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.",
ZipponError.SynthaxError,
self.toker.buffer,
token.loc.start,
token.loc.end,
);
}
return token;
}
/// Will check if what is compared is ok, like comparing if a string is superior to another string is not for example.
pub fn checkConditionValidity(
self: Self,
condition: Condition,
token: Token,
) ZipponError!void {
switch (condition.operation) {
.equal => switch (condition.data_type) {
.int, .float, .str, .bool, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, str, bool, date, time, datetime can be compare with =",
ZipponError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.different => switch (condition.data_type) {
.int, .float, .str, .bool, .date, .time, .datetime => {},
else => return printError(
"Error: Only int, float, str, bool, date, time, datetime can be compare with !=",
ZipponError.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 >=",
ZipponError.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 >",
ZipponError.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 <=",
ZipponError.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 <",
ZipponError.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.",
ZipponError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
.not_in => switch (condition.data_type) {
.link => {},
else => return printError(
"Error: Only link can be compare with !IN.",
ZipponError.ConditionError,
self.toker.buffer,
token.loc.start,
token.loc.end,
),
},
}
}