Switch to json-c and major refactoring
This commit is contained in:
parent
54d504c3de
commit
4472b765d0
|
@ -14,9 +14,10 @@ pub fn build(b: *std.build.Builder) void {
|
||||||
// Import autogenerated zigmod dependencies
|
// Import autogenerated zigmod dependencies
|
||||||
const deps = @import("deps.zig");
|
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
|
exe.use_stage1 = true; // for sqlite
|
||||||
deps.addAllTo(exe); // zigmod dependencies
|
deps.addAllTo(exe); // zigmod dependencies
|
||||||
|
exe.linkSystemLibrary("json-c");
|
||||||
exe.setTarget(target);
|
exe.setTarget(target);
|
||||||
exe.setBuildMode(mode);
|
exe.setBuildMode(mode);
|
||||||
exe.install();
|
exe.install();
|
||||||
|
|
116
src/Db.zig
116
src/Db.zig
|
@ -1,92 +1,27 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sqlite = @import("sqlite");
|
const sqlite = @import("sqlite");
|
||||||
|
|
||||||
|
// TODO: Make DB an object so we can just do Self.
|
||||||
|
|
||||||
const ObjectError = error{
|
const ObjectError = error{
|
||||||
IncompatibleObjectType, // No ?u32 "id" field
|
IncompatibleObjectType, // No ?u32 "id" field
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn persist(db: *sqlite.Db, obj: anytype) !void {
|
pub fn getLastId(db: *sqlite.Db) !i64 {
|
||||||
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 {
|
|
||||||
const row = try db.one(
|
const row = try db.one(
|
||||||
struct {
|
struct {
|
||||||
version: [128:0]u8,
|
rowid: i64,
|
||||||
},
|
},
|
||||||
"SELECT sqlite_version();",
|
"SELECT LAST_INSERT_ROWID();",
|
||||||
.{},
|
.{},
|
||||||
.{},
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (row) |row_i| {
|
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 {
|
pub fn init() !sqlite.Db {
|
||||||
|
@ -107,25 +42,34 @@ pub fn init() !sqlite.Db {
|
||||||
// TODO: Make a proper migration system.
|
// TODO: Make a proper migration system.
|
||||||
fn migrate(db: *sqlite.Db) !void {
|
fn migrate(db: *sqlite.Db) !void {
|
||||||
try db.exec(
|
try db.exec(
|
||||||
\\ CREATE TABLE IF NOT EXISTS item (
|
\\CREATE TABLE IF NOT EXISTS item (
|
||||||
\\ id INTEGER PRIMARY KEY,
|
\\ id INTEGER PRIMARY KEY
|
||||||
\\ name TEXT NOT NULL
|
\\);
|
||||||
\\ );
|
,
|
||||||
\\
|
.{},
|
||||||
\\ CREATE TABLE IF NOT EXISTS tag (
|
.{},
|
||||||
\\ id INTEGER PRIMARY KEY,
|
);
|
||||||
\\ name TEXT NOT NULL
|
|
||||||
\\ );
|
try db.exec(
|
||||||
\\
|
\\CREATE TABLE IF NOT EXISTS tag (
|
||||||
\\ CREATE TABLE IF NOT EXISTS item_tag (
|
\\ name TEXT PRIMARY KEY
|
||||||
|
\\);
|
||||||
|
,
|
||||||
|
.{},
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
try db.exec(
|
||||||
|
\\CREATE TABLE IF NOT EXISTS item_tag (
|
||||||
\\ item INTEGER,
|
\\ item INTEGER,
|
||||||
\\ tag INTEGER,
|
\\ tag TEXT,
|
||||||
|
\\ value TEXT,
|
||||||
\\
|
\\
|
||||||
\\ PRIMARY KEY (item, tag),
|
\\ PRIMARY KEY (item, tag),
|
||||||
\\
|
\\
|
||||||
\\ FOREIGN KEY (item) REFERENCES item(id) ON DELETE CASCADE,
|
\\ FOREIGN KEY (item) REFERENCES item(id) ON DELETE CASCADE,
|
||||||
\\ FOREIGN KEY (tag) REFERENCES tag(id) ON DELETE CASCADE
|
\\ FOREIGN KEY (tag) REFERENCES tag(name) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
\\ );
|
\\);
|
||||||
,
|
,
|
||||||
.{},
|
.{},
|
||||||
.{},
|
.{},
|
||||||
|
|
53
src/Item.zig
53
src/Item.zig
|
@ -1,7 +1,54 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Db = @import("Db.zig");
|
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
|
pub const Tag = struct{
|
||||||
name: []const u8,
|
// :0 for C compatility
|
||||||
|
name: [:0]const u8,
|
||||||
|
value: ?[:0]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
id: ?i64, // If null, the object hasn't been persisted
|
||||||
tags: ?[]Tag,
|
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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
20
src/Tag.zig
20
src/Tag.zig
|
@ -1,7 +1,23 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const sqlite = @import("sqlite");
|
||||||
|
|
||||||
const Db = @import("Db.zig");
|
const Db = @import("Db.zig");
|
||||||
const Item = @import("Item.zig");
|
const Item = @import("Item.zig");
|
||||||
|
|
||||||
id: ?u32,
|
const Self = @This();
|
||||||
|
|
||||||
name: []const u8,
|
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});
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
33
src/main.zig
33
src/main.zig
|
@ -1,15 +1,40 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sqlite = @import("sqlite");
|
const sqlite = @import("sqlite");
|
||||||
|
|
||||||
|
const json = @import("json.zig");
|
||||||
const Db = @import("Db.zig");
|
const Db = @import("Db.zig");
|
||||||
const Item = @import("Item.zig");
|
const Item = @import("Item.zig");
|
||||||
|
const Tag = @import("Tag.zig");
|
||||||
|
|
||||||
pub fn main() !void {
|
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();
|
var db = try Db.init();
|
||||||
|
|
||||||
const item = Item{ .name = "hola", .id = null, .tags = null };
|
var tags = [2]Item.Tag{
|
||||||
try Db.persist(&db, item);
|
.{ .name = "title", .value = "clean room" },
|
||||||
|
.{ .name = "task", .value = "explosion preventer" },
|
||||||
|
};
|
||||||
|
|
||||||
const version = try Db.queryTest(&db);
|
var item = Item{ .id = null, .tags = &tags };
|
||||||
std.debug.print("Version: {s}\n", .{version});
|
|
||||||
|
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()});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
2
|
2
|
||||||
git https://github.com/getty-zig/json commit-0c2e645fc1304ab57f9819445b91ea064da0b27b
|
git https://github.com/getty-zig/json commit-12db8754443faa01008e9e2218e675c44c8d0b48
|
||||||
git https://github.com/getty-zig/getty commit-102f17a
|
git https://github.com/getty-zig/getty commit-da51422
|
||||||
git https://github.com/ibokuri/concepts commit-05c7368
|
git https://github.com/ibokuri/concepts commit-05c7368
|
||||||
git https://github.com/vrischmann/zig-sqlite commit-5b8b472276829f75dced4dc405b66b7b986498b3
|
git https://github.com/vrischmann/zig-sqlite commit-5b8b472276829f75dced4dc405b66b7b986498b3
|
||||||
|
|
Loading…
Reference in New Issue