Switch to json-c and major refactoring

This commit is contained in:
Dendy 2022-10-21 18:58:44 +02:00
parent 54d504c3de
commit 4472b765d0
7 changed files with 200 additions and 101 deletions

View File

@ -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();

View File

@ -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
\\);
,
.{},
.{},

View File

@ -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 }
);
}
}
}

View File

@ -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});
}

66
src/json.zig Normal file
View File

@ -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

View File

@ -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()});
}

View File

@ -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