diff --git a/build.zig b/build.zig index 41124ce..4f02e7c 100644 --- a/build.zig +++ b/build.zig @@ -26,6 +26,10 @@ pub fn build(b: *std.build.Builder) void { exe.setTarget(target); exe.setBuildMode(mode); + const sql = std.build.Pkg{ + .name = "sql", + .source = std.build.FileSource.relative("src/sql/lib.zig"), + }; const util = std.build.Pkg{ .name = "util", .source = std.build.FileSource.relative("src/util/lib.zig"), @@ -35,9 +39,13 @@ pub fn build(b: *std.build.Builder) void { .source = std.build.FileSource.relative("src/http/lib.zig"), .dependencies = &[_]std.build.Pkg{util}, }; + exe.addPackage(sql); exe.addPackage(util); exe.addPackage(http); + exe.linkSystemLibrary("sqlite3"); + exe.linkLibC(); + exe.install(); addRunStep(b, exe); } diff --git a/src/main/api.zig b/src/main/api.zig index 62e7b83..f05d46c 100644 --- a/src/main/api.zig +++ b/src/main/api.zig @@ -1,5 +1,6 @@ const std = @import("std"); const util = @import("util"); +const sql = @import("sql"); const db = @import("./db.zig"); @@ -41,10 +42,14 @@ pub const ApiServer = struct { prng: std.rand.DefaultPrng, last_id: u64 = 0, + db2: sql.Sqlite, + pub fn init(alloc: std.mem.Allocator) !ApiServer { return ApiServer{ .db = try db.Database.init(alloc), .prng = std.rand.DefaultPrng.init(1998), + + .db2 = try sql.Sqlite.open("./test.db"), }; } diff --git a/src/sql/lib.zig b/src/sql/lib.zig new file mode 100644 index 0000000..afc17e9 --- /dev/null +++ b/src/sql/lib.zig @@ -0,0 +1,98 @@ +const std = @import("std"); +const c = @cImport({ + @cInclude("sqlite3.h"); +}); + +pub const Sqlite = struct { + db: *c.sqlite3, + + pub fn open(path: [:0]const u8) !Sqlite { + var db: ?*c.sqlite3 = undefined; + const err = c.sqlite3_open_v2(path, &db, c.SQLITE_OPEN_READWRITE | c.SQLITE_OPEN_CREATE, null); + if (err != c.SQLITE_OK) return error.UnknownError; + + return Sqlite{ + .db = db.?, + }; + } + + pub fn close(self: *Sqlite) void { + return c.sqlite3_close_v2(self.db); + } + + pub fn prepare(self: *Sqlite, sql: []const u8) !PreparedStmt { + var stmt: [*c]c.sqlite3_stmt = undefined; + const err = c.sqlite3_prepare_v2(self.db, sql.ptr, sql.len, &stmt, null); + if (err != c.SQLITE_OK) return error.UnknownError; + + return PreparedStmt{ .stmt = stmt }; + } +}; + +pub const Row = struct { + stmt: *c.sqlite3_stmt, + + pub fn getI64(self: *Row, idx: u15) !i64 { + return @intCast(i64, c.sqlite3_column_int64(self.stmt, idx)); + } + + pub fn getText(self: *Row, idx: u15, buf: []u8) ![]u8 { + const ptr = c.sqlite3_column_text(self.stmt, idx); + + const size = c.sqlite3_column_bytes(self.stmt, idx); + if (size > buf.len) return error.StreamTooLong; + for (ptr[0..size]) |ch, i| buf[i] = ch; + + return buf[0..size]; + } + + pub fn getTextAlloc(self: *Row, idx: u15, alloc: std.mem.Allocator) ![]u8 { + const size = c.sqlite3_column_bytes(self.stmt, idx); + var buf = try alloc.alloc(u8, size); + errdefer alloc.free(buf); + + return self.getText(idx, buf); + } +}; + +pub const PreparedStmt = struct { + stmt: *c.sqlite3_stmt, + + pub fn bindNull(self: *PreparedStmt, idx: u15) !void { + return switch (c.sqlite3_bind_null(self.stmt, idx)) { + .SQLITE_OK => void, + else => error.UnknownError, + }; + } + + pub fn bindText(self: *PreparedStmt, idx: u15, str: []const u8) !void { + return switch (c.sqlite3_bind_text(self.stmt, idx, str.ptr, str.len, c.SQLITE_TRANSIENT)) { + .SQLITE_OK => void, + else => error.UnknownError, + }; + } + + pub fn bindI64(self: *PreparedStmt, idx: u15, val: i64) !void { + return switch (c.sqlite3_bind_int64(self.stmt, idx, val)) { + .SQLITE_OK => void, + else => error.UnknownError, + }; + } + + pub fn step(self: *PreparedStmt) !?Row { + return switch (c.sqlite3_step(self.stmt)) { + .SQLITE_ROW => Row{ .stmt = self.stmt }, + .SQLITE_DONE => null, + + else => error.UnknownError, + }; + } + + pub fn finalize(self: *PreparedStmt) void { + _ = c.sqlite3_finalize(self.stmt); + } + + pub fn reset(self: *PreparedStmt) void { + _ = c.sqlite3_reset(self.stmt); + } +};