sql-zig

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

main.zig (4082B)


      1 const std = @import("std");
      2 const log = @import("log.zig").scoped_log_t(.db);
      3 
      4 /// Database abstraction layer
      5 pub const Db = @This();
      6 
      7 /// Type erased pointer to actual implementation, and reference to implementation functions
      8 /// Inspired by std.mem.Allocator
      9 ptr: *anyopaque,
     10 vtable: VTable,
     11 
     12 fn init(ptr: *anyopaque, vtable: VTable) Db {
     13     return Db{
     14         .ptr = ptr,
     15         .vtable = vtable,
     16     };
     17 }
     18 
     19 pub const OpenError = error{ Failed, NotThreadSafe } || std.mem.Allocator.Error;
     20 pub const PrepareError = error{Failed} || std.mem.Allocator.Error;
     21 pub const BindError = error{};
     22 pub const StepError = error{Failed} || std.mem.Allocator.Error;
     23 pub const ColumnError = error{WrongType} || std.fmt.ParseIntError;
     24 
     25 // Dispatcher for concrete implementations. Inspired by std.mem.Allocator.
     26 const VTable = struct {
     27     prepare: *const fn (*anyopaque, query: [:0]const u8) PrepareError!*anyopaque,
     28     // bind: *const fn (db: *anyopaque, stmt: *anyopaque, idx: usize, val: anytype) BindError!void,
     29     step: *const fn (db: *anyopaque, stmt: *anyopaque) StepError!bool,
     30     // TODO support more types
     31     // TODO think of something clever to avoid this function proliferation.
     32     // Values returned should live at least until next call to .step()
     33     column_i64: *const fn (db: *anyopaque, stmt: *anyopaque, idx: u31) ColumnError!?i64,
     34     column_slice_const_u8: *const fn (db: *anyopaque, stmt: *anyopaque, idx: u31) ColumnError!?[:0]const u8,
     35     close_stmt: *const fn (db: *anyopaque, stmt: *anyopaque) void,
     36     close_db: *const fn (db: *anyopaque) void,
     37 };
     38 
     39 pub const Stmt = struct {
     40     db: *Db,
     41     ptr: *anyopaque,
     42 
     43     fn bind(self: *Stmt, idx: u31, val: anytype) !void {
     44         try self.db.vtable.bind(self.db.ptr, self.ptr, idx, val);
     45     }
     46     fn bind_named(self: *Stmt, name: [:0]const u8, val: anytype) !void {
     47         try self.db.vtable.bind_named(self.db.ptr, self.ptr, name, val);
     48     }
     49     /// Advance the result set to the next row.
     50     pub fn step(self: *Stmt) !bool {
     51         return try self.db.vtable.step(self.db.ptr, self.ptr);
     52     }
     53     /// Read a column
     54     pub fn column(self: *Stmt, comptime T: type, index: u31) !?T {
     55         switch (@typeInfo(T)) {
     56             .Int => |intinfo| {
     57                 if (intinfo.signedness != .signed) @compileError("integer type i64 only is supported in Stmt#column");
     58                 if (intinfo.bits != 64) @compileError("integer type i64 only is supported in Stmt#column");
     59                 return try self.db.vtable.column_i64(self.db.ptr, self.ptr, index);
     60             },
     61             .Pointer => |ptrinfo| {
     62                 if (ptrinfo.size != .Slice) @compileError("pointer type []const u8 only is supported in Stmt#column");
     63                 if (ptrinfo.child != u8) @compileError("pointer type []const u8 only is supported in Stmt#column");
     64                 return try self.db.vtable.column_slice_const_u8(self.db.ptr, self.ptr, index);
     65             },
     66             else => @compileError("unhandled type " ++ @tagName(@typeInfo(T)) ++ " in Stmt#column"),
     67         }
     68     }
     69     pub fn close(self: *Stmt) void {
     70         self.db.vtable.close_stmt(self.db.ptr, self.ptr);
     71     }
     72 };
     73 
     74 /// Interface for running queries
     75 /// params should be a tuple with values for placeholders.
     76 pub fn query(self: *Db, comptime qry: [:0]const u8, params: anytype) !Stmt {
     77     const ti = @typeInfo(@TypeOf(params));
     78     if (ti != .Struct) @compileError("Db.query params must be a tuple struct but it's a " ++ @tagName(ti));
     79     const si = ti.Struct;
     80     if (!si.is_tuple) @compileError("Db.query params must be a tuple");
     81 
     82     const stmt_ptr = try self.vtable.prepare(self.ptr, qry);
     83     var stmt = Stmt{ .db = self, .ptr = stmt_ptr };
     84     inline for (0..si.fields.len) |idx| {
     85         try stmt.bind(idx, params[idx]);
     86     }
     87     return stmt;
     88 }
     89 
     90 /// Shortcut for query/step/close
     91 pub fn exec(self: *Db, comptime qry: [:0]const u8) !void {
     92     var stmt = try self.query(qry, .{});
     93     defer stmt.close();
     94     _ = try stmt.step();
     95 }
     96 
     97 pub fn close(self: *Db) void {
     98     self.vtable.close_db(self.ptr);
     99 }
    100 
    101 test {
    102     _ = @import("test.zig");
    103 }