Initial support for sqlite3
This commit is contained in:
parent
0bce7ba0fb
commit
b99bd55b72
|
@ -16,6 +16,7 @@ pub fn build(b: *std.build.Builder) void {
|
||||||
exe.linkLibC();
|
exe.linkLibC();
|
||||||
|
|
||||||
exe.linkSystemLibrary("curl");
|
exe.linkSystemLibrary("curl");
|
||||||
|
exe.linkSystemLibrary("sqlite3");
|
||||||
|
|
||||||
exe.setTarget(target);
|
exe.setTarget(target);
|
||||||
exe.setBuildMode(mode);
|
exe.setBuildMode(mode);
|
||||||
|
@ -34,6 +35,13 @@ pub fn build(b: *std.build.Builder) void {
|
||||||
exe_tests.setTarget(target);
|
exe_tests.setTarget(target);
|
||||||
exe_tests.setBuildMode(mode);
|
exe_tests.setBuildMode(mode);
|
||||||
|
|
||||||
|
const sqlite_tests = b.addTest("src/sqlite.zig");
|
||||||
|
sqlite_tests.setTarget(target);
|
||||||
|
sqlite_tests.setBuildMode(mode);
|
||||||
|
sqlite_tests.linkSystemLibrary("sqlite3");
|
||||||
|
sqlite_tests.linkLibC();
|
||||||
|
|
||||||
const test_step = b.step("test", "Run unit tests");
|
const test_step = b.step("test", "Run unit tests");
|
||||||
test_step.dependOn(&exe_tests.step);
|
test_step.dependOn(&exe_tests.step);
|
||||||
|
test_step.dependOn(&sqlite_tests.step);
|
||||||
}
|
}
|
||||||
|
|
11
src/main.zig
11
src/main.zig
|
@ -4,6 +4,7 @@ const json = std.json;
|
||||||
const twitch = @import("twitch.zig");
|
const twitch = @import("twitch.zig");
|
||||||
const Client = @import("client.zig").Client;
|
const Client = @import("client.zig").Client;
|
||||||
const webhook = @import("webhook.zig");
|
const webhook = @import("webhook.zig");
|
||||||
|
const sqlite = @import("sqlite.zig");
|
||||||
|
|
||||||
const Config = struct {
|
const Config = struct {
|
||||||
token: []const u8,
|
token: []const u8,
|
||||||
|
@ -33,6 +34,13 @@ const User = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() anyerror!void {
|
pub fn main() anyerror!void {
|
||||||
|
var db = try sqlite.Database.open("a.db");
|
||||||
|
defer {
|
||||||
|
_ = db.close() catch |e| {
|
||||||
|
std.log.err("Failed to close db : {}", .{e});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
var allocator = arena.allocator();
|
var allocator = arena.allocator();
|
||||||
|
|
||||||
|
@ -51,11 +59,10 @@ pub fn main() anyerror!void {
|
||||||
client.deinit();
|
client.deinit();
|
||||||
|
|
||||||
try Client.cleanup();
|
try Client.cleanup();
|
||||||
|
|
||||||
arena.deinit();
|
arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateAlert(allocator: std.mem.Allocator, client: *Client, config: *Config, headers: *std.StringHashMap([]const u8)) anyerror!void{
|
pub fn updateAlert(allocator: std.mem.Allocator, client: *Client, config: *Config, headers: *std.StringHashMap([]const u8)) anyerror!void {
|
||||||
var request = std.ArrayList(u8).init(allocator);
|
var request = std.ArrayList(u8).init(allocator);
|
||||||
|
|
||||||
try request.appendSlice("https://api.twitch.tv/helix/streams?");
|
try request.appendSlice("https://api.twitch.tv/helix/streams?");
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const sqlite3 = @cImport({
|
||||||
|
@cInclude("sqlite3.h");
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const Database = struct {
|
||||||
|
db: ?*sqlite3.sqlite3 = null,
|
||||||
|
|
||||||
|
pub fn open(path: [*:0]const u8) anyerror!@This() {
|
||||||
|
var ptr: ?*sqlite3.sqlite3 = null;
|
||||||
|
|
||||||
|
var rc = sqlite3.sqlite3_open(path, &ptr);
|
||||||
|
if (rc > 0) {
|
||||||
|
std.log.err("Can't open database: {s}\n", .{sqlite3.sqlite3_errmsg(ptr)});
|
||||||
|
return error.FailedToOpenDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
return @This(){
|
||||||
|
.db = ptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(self: *@This()) anyerror!void {
|
||||||
|
var rc = sqlite3.sqlite3_close(self.db);
|
||||||
|
if (rc > 0) {
|
||||||
|
std.log.err("Can't close database: {s}\n", .{sqlite3.sqlite3_errmsg(self.db)});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare(self: *@This(), query: []const u8) anyerror!Statement {
|
||||||
|
var res: ?*sqlite3.sqlite3_stmt = null;
|
||||||
|
var rc = sqlite3.sqlite3_prepare_v2(self.db, query.ptr, @intCast(c_int, query.len), &res, 0);
|
||||||
|
|
||||||
|
if (rc != sqlite3.SQLITE_OK) {
|
||||||
|
std.log.err("failed to fetch data: {s}\n", .{sqlite3.sqlite3_errmsg(self.db)});
|
||||||
|
return error.FailedToFetchData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res) |ptr| {
|
||||||
|
return Statement{
|
||||||
|
.db = self,
|
||||||
|
.statement = ptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return error.NullPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec(self: *@This(), queries: [:0]const u8) anyerror!void { // TODO ADD 0 IF NEEDED AT COMPILE TIME
|
||||||
|
var errorMsg: ?[*:0]u8 = null;
|
||||||
|
|
||||||
|
var rc = sqlite3.sqlite3_exec(self.db, queries, null, null, &errorMsg);
|
||||||
|
if (rc != sqlite3.SQLITE_OK) {
|
||||||
|
std.log.err("failed to execute queries: {s}\n", .{errorMsg}); // TODO
|
||||||
|
sqlite3.sqlite3_free(errorMsg);
|
||||||
|
return error.FailedToExecuteQueries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Statement = struct {
|
||||||
|
db: *Database,
|
||||||
|
statement: *sqlite3.sqlite3_stmt,
|
||||||
|
|
||||||
|
pub fn bind(self: *@This(), index: isize, value: anytype) anyerror!void {
|
||||||
|
const T = @TypeOf(value);
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Float, .ComptimeFloat => {
|
||||||
|
var rc = sqlite3.sqlite3_bind_double(self.statement, @intCast(c_int, index), value);
|
||||||
|
if (rc != sqlite3.SQLITE_OK) {
|
||||||
|
std.log.err("failed to bind parameter: {s}\n", .{sqlite3.sqlite3_errmsg(self.db.db)});
|
||||||
|
return error.FailedToBindParameter;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Int, .ComptimeInt => {
|
||||||
|
var rc = sqlite3.sqlite3_bind_int(self.statement, @intCast(c_int, index), value);
|
||||||
|
if (rc != sqlite3.SQLITE_OK) {
|
||||||
|
std.log.err("failed to bind parameter: {s}\n", .{sqlite3.sqlite3_errmsg(self.db.db)});
|
||||||
|
return error.FailedToBindParameter;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Union => {
|
||||||
|
if (T == U8Array) {
|
||||||
|
switch (value) {
|
||||||
|
U8ArrayTypeTag.text => {
|
||||||
|
var rc = sqlite3.sqlite3_bind_text(self.statement, @intCast(c_int, index), value.text.ptr, @intCast(c_int, value.text.len), sqlite3.SQLITE_TRANSIENT);
|
||||||
|
if (rc != sqlite3.SQLITE_OK) {
|
||||||
|
std.log.err("failed to bind parameter: {s}\n", .{sqlite3.sqlite3_errmsg(self.db.db)});
|
||||||
|
return error.FailedToBindParameter;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
U8ArrayTypeTag.bytes => {
|
||||||
|
var rc = sqlite3.sqlite3_bind_blob(self.statement, @intCast(c_int, index), value.bytes.ptr, @intCast(c_int, value.bytes.len), sqlite3.SQLITE_TRANSIENT);
|
||||||
|
if (rc != sqlite3.SQLITE_OK) {
|
||||||
|
std.log.err("failed to bind parameter: {s}\n", .{sqlite3.sqlite3_errmsg(self.db.db)});
|
||||||
|
return error.FailedToBindParameter;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@compileError("Unable to bind type '" ++ @typeName(T) ++ "'");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to bind type '" ++ @typeName(T) ++ "'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *@This()) bool {
|
||||||
|
var rc = sqlite3.sqlite3_step(self.statement);
|
||||||
|
|
||||||
|
// TODO ERROR ?
|
||||||
|
return rc == sqlite3.SQLITE_ROW;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec(self: *@This()) anyerror!void {
|
||||||
|
var rc = sqlite3.sqlite3_step(self.statement);
|
||||||
|
|
||||||
|
if (rc == sqlite3.SQLITE_ROW) {
|
||||||
|
return anyerror.ExecFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize(self: *@This()) void {
|
||||||
|
_ = sqlite3.sqlite3_finalize(self.statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch(self: *@This(), args: anytype) anyerror!void {
|
||||||
|
const ArgsType = @TypeOf(args);
|
||||||
|
|
||||||
|
if (@typeInfo(ArgsType) != .Struct) {
|
||||||
|
@compileError("Expected tuple or struct argument, found " ++ @typeName(ArgsType));
|
||||||
|
}
|
||||||
|
|
||||||
|
comptime var index = 0;
|
||||||
|
inline while (index < args.len) : (index += 1) {
|
||||||
|
const arg = args[index];
|
||||||
|
|
||||||
|
comptime var T = @TypeOf(arg);
|
||||||
|
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Pointer => |ptr_info| switch (ptr_info.size) {
|
||||||
|
.One => switch (@typeInfo(ptr_info.child)) {
|
||||||
|
.Float, .ComptimeFloat => {
|
||||||
|
arg.* = sqlite3.sqlite3_column_double(self.statement, @intCast(c_int, index));
|
||||||
|
},
|
||||||
|
.Int, .ComptimeInt => {
|
||||||
|
arg.* = sqlite3.sqlite3_column_int(self.statement, @intCast(c_int, index));
|
||||||
|
},
|
||||||
|
.Union => {
|
||||||
|
T = @TypeOf(arg.*);
|
||||||
|
if (T == U8Array) {
|
||||||
|
switch (arg.*) {
|
||||||
|
U8ArrayTypeTag.text => {
|
||||||
|
var text: [*c]const u8 = sqlite3.sqlite3_column_text(self.statement, @intCast(c_int, index));
|
||||||
|
|
||||||
|
if (text != null) {
|
||||||
|
var size = sqlite3.sqlite3_column_bytes(self.statement, @intCast(c_int, index));
|
||||||
|
|
||||||
|
arg.text = text[0..@intCast(usize, size)];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
U8ArrayTypeTag.bytes => {
|
||||||
|
var blob_ptr: ?*const anyopaque = sqlite3.sqlite3_column_blob(self.statement, @intCast(c_int, index));
|
||||||
|
|
||||||
|
if (blob_ptr != null) {
|
||||||
|
var blob = @ptrCast([*]const u8, blob_ptr);
|
||||||
|
var size = sqlite3.sqlite3_column_bytes(self.statement, @intCast(c_int, index));
|
||||||
|
|
||||||
|
arg.bytes = blob[0..@intCast(usize, size)]; // TODO TEST
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@compileError("Unable to fetch type '" ++ @typeName(T) ++ "'");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to fetch type '" ++ @typeName(T) ++ "'"),
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("Unable to fetch type '" ++ @typeName(T) ++ "'"),
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to fetch type '" ++ @typeName(T) ++ "'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const U8ArrayTypeTag = enum {
|
||||||
|
text,
|
||||||
|
bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const U8Array = union(U8ArrayTypeTag) {
|
||||||
|
text: []const u8,
|
||||||
|
bytes: []const u8,
|
||||||
|
|
||||||
|
pub fn text(value: []const u8) @This() {
|
||||||
|
return @This(){ .text = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bytes(value: []const u8) @This() {
|
||||||
|
return @This(){ .bytes = value };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For whatever reason this test make the compiler segfault.
|
||||||
|
fn testCreationBd() anyerror!void {
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
var db = try Database.open(":memory:");
|
||||||
|
|
||||||
|
defer {
|
||||||
|
_ = db.close() catch |e| {
|
||||||
|
std.log.err("Failed to close db : {}", .{e});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try db.exec("CREATE TABLE A(A INTEGER, B TEXT, C REAL, D BLOB);");
|
||||||
|
|
||||||
|
const first_blob_test = [_]u8{ 0, 1, 2 };
|
||||||
|
const second_blob_test = [_]u8{0} ** 1000;
|
||||||
|
|
||||||
|
{
|
||||||
|
var st = try db.prepare("INSERT INTO A VALUES(?, ?, ?, ?)");
|
||||||
|
try st.bind(1, 150);
|
||||||
|
try st.bind(2, U8Array.text("This is the first test"));
|
||||||
|
try st.bind(3, 3.45);
|
||||||
|
try st.bind(4, U8Array.bytes(&first_blob_test));
|
||||||
|
|
||||||
|
try st.exec();
|
||||||
|
|
||||||
|
st.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var st = try db.prepare("INSERT INTO A VALUES(?, ?, ?, ?)");
|
||||||
|
try st.bind(1, 175);
|
||||||
|
try st.bind(2, U8Array.text("This is another test"));
|
||||||
|
try st.bind(3, 156.4);
|
||||||
|
try st.bind(4, U8Array.bytes(&second_blob_test));
|
||||||
|
|
||||||
|
try st.exec();
|
||||||
|
|
||||||
|
st.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = try db.prepare("SELECT * FROM A");
|
||||||
|
|
||||||
|
var a: isize = undefined;
|
||||||
|
var b: U8Array = U8Array.text(undefined);
|
||||||
|
var c: f64 = undefined;
|
||||||
|
var d: U8Array = U8Array.bytes(undefined);
|
||||||
|
|
||||||
|
{
|
||||||
|
assert(query.next());
|
||||||
|
try query.fetch(.{ &a, &b, &c, &d });
|
||||||
|
|
||||||
|
assert(a == 150);
|
||||||
|
assert(std.mem.eql(u8, "This is the first test", b.text));
|
||||||
|
assert(c == 3.45);
|
||||||
|
assert(std.mem.eql(u8, &first_blob_test, d.bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
assert(query.next());
|
||||||
|
try query.fetch(.{ &a, &b, &c, &d });
|
||||||
|
|
||||||
|
assert(a == 175);
|
||||||
|
assert(std.mem.eql(u8, "This is another test", b.text));
|
||||||
|
assert(c == 156.4);
|
||||||
|
assert(std.mem.eql(u8, &second_blob_test, d.bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
query.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "test-creation-bd" {
|
||||||
|
try testCreationBd();
|
||||||
|
}
|
Loading…
Reference in New Issue