diff --git a/build.zig b/build.zig index c5adf0d..271f5fc 100644 --- a/build.zig +++ b/build.zig @@ -14,9 +14,10 @@ pub fn build(b: *std.build.Builder) void { // Import autogenerated zigmod dependencies const deps = @import("deps.zig"); - const exe = b.addExecutable("uos", "src/main.zig"); + const exe = b.addExecutable("mgtzm", "src/main.zig"); exe.use_stage1 = true; // for sqlite deps.addAllTo(exe); // zigmod dependencies + exe.linkSystemLibrary("json-c"); exe.setTarget(target); exe.setBuildMode(mode); exe.install(); diff --git a/src/Db.zig b/src/Db.zig index 905baa9..aa96b4c 100644 --- a/src/Db.zig +++ b/src/Db.zig @@ -1,92 +1,27 @@ const std = @import("std"); const sqlite = @import("sqlite"); +// TODO: Make DB an object so we can just do Self. + const ObjectError = error{ IncompatibleObjectType, // No ?u32 "id" field }; -pub fn persist(db: *sqlite.Db, obj: anytype) !void { - const T = @TypeOf(obj); - - // Check that object is a valid type for the DB system. - // It should have an "id" field and be of type "?u32" - if (!@hasField(T, "id") or @TypeOf(@field(obj, "id")) != ?u32) { - return ObjectError.IncompatibleObjectType; - } - - // Tried to persist something that is already persisted - if (@field(obj, "id") != null) { - std.debug.print("Can't persist an object with an id.\n", .{}); - return; - } - - const fields = std.meta.fields(T); - - const query_init = "INSTERT INTO " ++ @typeName(T) ++ " ("; - - const sep = ", "; - var buf: [1024]u8 = .{0} ** 1024; - var offset: usize = 0; - - std.mem.copy(u8, buf[offset..], query_init); - offset += query_init.len; - - // Keep track of how many fields to instert - comptime var count: u8 = 0; - inline for (fields) |field_i| { - // Do not input "id" field. - comptime if (std.mem.eql(u8, field_i.name, "id")) { - continue; - }; - - // Do not input array fields (they got their own table) - comptime switch (@typeInfo(field_i.field_type)) { - .Array => continue, - .Pointer => |info| switch (info.size) { - .One => {}, - .Many, .C, .Slice => continue, - }, - else => {}, - }; - - count += 1; - - std.mem.copy(u8, buf[offset..], field_i.name); - offset += field_i.name.len; - std.mem.copy(u8, buf[offset..], sep); - offset += sep.len; - } - offset -= sep.len; - - const query_end = ") VALUES (" ++ ("?, " ** (count - 1)) ++ "?);"; - - std.mem.copy(u8, buf[offset..], query_end); - offset += query_end.len; - - std.debug.print("{s}\n", .{buf[0..offset]}); - - _ = db; - - //const query = "INSERT INTO item (name) VALUES (?);"; - - //try db.exec(query, .{}, .{ .name = name }); -} - -pub fn queryTest(db: *sqlite.Db) ![]const u8 { +pub fn getLastId(db: *sqlite.Db) !i64 { const row = try db.one( struct { - version: [128:0]u8, + rowid: i64, }, - "SELECT sqlite_version();", + "SELECT LAST_INSERT_ROWID();", .{}, .{}, ); if (row) |row_i| { - return std.mem.sliceTo(&row_i.version, 0); + return row_i.rowid; } - return "N/A"; + return 0; } pub fn init() !sqlite.Db { @@ -107,25 +42,34 @@ pub fn init() !sqlite.Db { // TODO: Make a proper migration system. fn migrate(db: *sqlite.Db) !void { try db.exec( - \\ CREATE TABLE IF NOT EXISTS item ( - \\ id INTEGER PRIMARY KEY, - \\ name TEXT NOT NULL - \\ ); + \\CREATE TABLE IF NOT EXISTS item ( + \\ id INTEGER PRIMARY KEY + \\); + , + .{}, + .{}, + ); + + try db.exec( + \\CREATE TABLE IF NOT EXISTS tag ( + \\ name TEXT PRIMARY KEY + \\); + , + .{}, + .{}, + ); + + try db.exec( + \\CREATE TABLE IF NOT EXISTS item_tag ( + \\ item INTEGER, + \\ tag TEXT, + \\ value TEXT, \\ - \\ CREATE TABLE IF NOT EXISTS tag ( - \\ id INTEGER PRIMARY KEY, - \\ name TEXT NOT NULL - \\ ); + \\ PRIMARY KEY (item, tag), \\ - \\ CREATE TABLE IF NOT EXISTS item_tag ( - \\ item INTEGER, - \\ tag INTEGER, - \\ - \\ PRIMARY KEY (item, tag), - \\ - \\ FOREIGN KEY (item) REFERENCES item(id) ON DELETE CASCADE, - \\ FOREIGN KEY (tag) REFERENCES tag(id) ON DELETE CASCADE - \\ ); + \\ FOREIGN KEY (item) REFERENCES item(id) ON DELETE CASCADE, + \\ FOREIGN KEY (tag) REFERENCES tag(name) ON DELETE CASCADE ON UPDATE CASCADE + \\); , .{}, .{}, diff --git a/src/Item.zig b/src/Item.zig index 6c652d8..60187d2 100644 --- a/src/Item.zig +++ b/src/Item.zig @@ -1,7 +1,54 @@ const std = @import("std"); const Db = @import("Db.zig"); -const Tag = @import("Tag.zig"); +const sqlite = @import("sqlite"); +const Self = @This(); -id: ?u32, // If null, the object hasn't been persisted -name: []const u8, +pub const Tag = struct{ + // :0 for C compatility + name: [:0]const u8, + value: ?[:0]const u8, +}; + +id: ?i64, // If null, the object hasn't been persisted tags: ?[]Tag, + +pub fn persist(self: *Self, db: *sqlite.Db) !void { + /////////////////////////////////// + // ** Insert item ** + ///////////////////////////////// + + // Insert only if there's no ID. + if (self.id == null) { + const query = "INSERT INTO item VALUES(NULL);"; + try db.exec(query, .{}, .{}); + + // Update the ID field + self.id = try Db.getLastId(db); + } + + /////////////////////////////////// + // ** Insert tags ** + ///////////////////////////////// + + // If the tags haven't been initialized, don't touch anything + if (self.tags) |tags| { + // TODO: Remove unused tag relations + // If the count drops to zero, delete. + + // Create new tags if they don't exist + for (tags) |*tags_i| { + try db.exec( + "INSERT INTO tag (name) VALUES (?);", + .{}, + .{ .name = tags_i.name } + ); + + try db.exec( + "INSERT INTO item_tag (item, tag, value) VALUES (?, ?, ?);", + .{}, + .{ .item = self.id, .tag = tags_i.name, .value = tags_i.value } + ); + } + } + +} diff --git a/src/Tag.zig b/src/Tag.zig index 68ebfbd..3749996 100644 --- a/src/Tag.zig +++ b/src/Tag.zig @@ -1,7 +1,23 @@ const std = @import("std"); +const sqlite = @import("sqlite"); + const Db = @import("Db.zig"); const Item = @import("Item.zig"); -id: ?u32, +const Self = @This(); + name: []const u8, -items: ?[]Item, + +pub fn persist(self: *Self, db: *sqlite.Db) !void { + /////////////////////////////////// + // ** Insert item ** + ///////////////////////////////// + + // TODO: Test if tag exists + if (true) { + const query = "INSERT INTO tag (name) VALUES (?);"; + try db.exec(query, .{}, .{ .name = self.name }); + } + + std.debug.print("{any}", .{self}); +} diff --git a/src/json.zig b/src/json.zig new file mode 100644 index 0000000..5dc0987 --- /dev/null +++ b/src/json.zig @@ -0,0 +1,66 @@ +const c = @cImport({ + @cInclude("json.h"); +}); +const std = @import("std"); + +// NOTE: I try to be faithful to the original namings but also try to +// adapt some things to a more zig-ish style. + +pub const Obj = struct { + obj: *c.json_object, + + // TODO: Port type attribute to a zig enum and assign it upon creation + + /////////////////////////////////// + // ** JSON object types creation ** + ///////////////////////////////// + + pub fn newObject() Obj { + return Obj{ .obj = c.json_object_new_object().? }; + } + pub fn newArray() Obj { + return Obj{ .obj = c.json_object_new_array().? }; + } + pub fn newString(s: [*c]const u8) Obj { + return Obj{ .obj = c.json_object_new_string(s).? }; + } + pub const newInt = newInt32; + pub fn newInt32(i: i32) Obj { + return Obj{ .obj = c.json_object_new_int(i).? }; + } + pub fn newInt64(i: i32) Obj { + return Obj{ .obj = c.json_object_new_int64(i).? }; + } + + pub fn deinit(self: *Obj) void { + _ = c.json_object_put(self.obj); + } + + pub fn toString(self: *Obj) []const u8 { + // TODO: Allow passing of flags as an enum like the SDL2 binding + return std.mem.sliceTo(c.json_object_to_json_string(self.obj), 0); + } + + /////////////////////////////////// + // ** Object functions ** + ///////////////////////////////// + + // TODO: Create an error for type checking. + + pub fn objectGet(self: *Obj, key: [*c]const u8, value: **Obj) void { + // TODO: Check type and null return as errors + _ = c.json_object_object_get_ex(self.obj, key, value); + } + + pub fn objectAdd(self: *Obj, key: [*c]const u8, value: ?*Obj) void { + // TODO: Check type and error return + + // We need the json-c object or null, not the zig object + const o = if (value) |i| i.obj else null; + + _ = c.json_object_object_add(self.obj, key, o); + } +}; + +// TODO: Create wrapper types that statically check and know there +// won't be any problems diff --git a/src/main.zig b/src/main.zig index 46db93c..856a04c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,15 +1,40 @@ const std = @import("std"); const sqlite = @import("sqlite"); +const json = @import("json.zig"); const Db = @import("Db.zig"); const Item = @import("Item.zig"); +const Tag = @import("Tag.zig"); pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + var db = try Db.init(); - const item = Item{ .name = "hola", .id = null, .tags = null }; - try Db.persist(&db, item); + var tags = [2]Item.Tag{ + .{ .name = "title", .value = "clean room" }, + .{ .name = "task", .value = "explosion preventer" }, + }; - const version = try Db.queryTest(&db); - std.debug.print("Version: {s}\n", .{version}); + var item = Item{ .id = null, .tags = &tags }; + + try item.persist(&db); + + var jobj = json.Obj.newObject(); + + for (tags) |tag_i| { + const jtag = if (tag_i.value) |value| + &json.Obj.newString(value) + else + null; + + jobj.objectAdd(tag_i.name, jtag); + } + + defer jobj.deinit(); + _ = alloc; + + std.debug.print("{s}\n", .{jobj.toString()}); } diff --git a/zigmod.lock b/zigmod.lock index 561628a..3c5b910 100644 --- a/zigmod.lock +++ b/zigmod.lock @@ -1,5 +1,5 @@ 2 -git https://github.com/getty-zig/json commit-0c2e645fc1304ab57f9819445b91ea064da0b27b -git https://github.com/getty-zig/getty commit-102f17a +git https://github.com/getty-zig/json commit-12db8754443faa01008e9e2218e675c44c8d0b48 +git https://github.com/getty-zig/getty commit-da51422 git https://github.com/ibokuri/concepts commit-05c7368 git https://github.com/vrischmann/zig-sqlite commit-5b8b472276829f75dced4dc405b66b7b986498b3