fediglam/src/sql/lib.zig

276 lines
7.9 KiB
Zig
Raw Normal View History

2022-07-15 00:58:08 +00:00
const std = @import("std");
2022-09-11 11:38:46 +00:00
const util = @import("util");
2022-07-15 00:58:08 +00:00
2022-09-11 08:55:20 +00:00
const postgres = @import("./postgres.zig");
const sqlite = @import("./sqlite.zig");
const Allocator = std.mem.Allocator;
2022-07-16 19:00:33 +00:00
2022-09-11 08:55:20 +00:00
pub const Type = enum {
postgres,
sqlite,
2022-07-15 00:58:08 +00:00
};
2022-09-11 08:55:20 +00:00
pub const Config = union(Type) {
postgres: struct {
conn_str: [:0]const u8,
},
sqlite: struct {
file_path: [:0]const u8,
},
2022-07-15 00:58:08 +00:00
};
2022-09-11 08:55:20 +00:00
//pub const OpenError = sqlite.OpenError | postgres.OpenError;
2022-09-11 11:38:46 +00:00
const RawResults = union(Type) {
2022-09-11 08:55:20 +00:00
postgres: postgres.Results,
sqlite: sqlite.Results,
2022-07-15 00:58:08 +00:00
2022-09-11 11:38:46 +00:00
fn finish(self: RawResults) void {
2022-09-11 08:55:20 +00:00
switch (self) {
.postgres => |pg| pg.finish(),
.sqlite => |lite| lite.finish(),
}
2022-07-16 19:00:33 +00:00
}
2022-09-11 11:38:46 +00:00
};
2022-07-16 19:00:33 +00:00
2022-09-11 11:38:46 +00:00
// Represents a set of results.
// row() must be called until it returns null, or the query may not complete
// Must be deallocated by a call to finish()
pub fn Results(comptime result_types: []const type) type {
return struct {
const Self = @This();
const RowTuple = std.meta.Tuple(result_types);
2022-07-15 00:58:08 +00:00
2022-09-11 11:38:46 +00:00
underlying: RawResults,
pub fn finish(self: Self) void {
self.underlying.finish();
}
// can be used as an optimization to reduce memory reallocation
// only works on postgres
pub fn rowCount(self: Self) ?usize {
return switch (self.underlying) {
.postgres => |pg| pg.rowCount(),
.sqlite => null, // not possible without repeating the query
};
}
pub fn row(self: *Self, alloc: ?Allocator) !?RowTuple {
const row_val = switch (self.underlying) {
.postgres => |*pg| if (try pg.row()) |r| Row{ .postgres = r } else return null,
.sqlite => |*lite| if (try lite.row()) |r| Row{ .sqlite = r } else return null,
};
var result: RowTuple = undefined;
var fields_allocated = [_]bool{false} ** result.len;
errdefer {
inline for (result_types) |_, i| {
if (fields_allocated[i]) util.deepFree(alloc, result[i]);
}
}
inline for (result_types) |T, i| {
result[i] = try row_val.get(T, i, alloc);
fields_allocated[i] = true;
}
return result;
}
};
}
2022-07-24 04:14:46 +00:00
2022-09-11 08:55:20 +00:00
// Row is invalidated by the next call to result.row()
2022-09-11 11:38:46 +00:00
const Row = union(Type) {
2022-09-11 08:55:20 +00:00
postgres: postgres.Row,
sqlite: sqlite.Row,
// Returns a value of type T from the zero-indexed column given by idx.
// Not all types require an allocator to be present. If an allocator is needed but
// not required, it will return error.AllocatorRequired.
// The caller is responsible for deallocating T, if relevant.
2022-09-11 11:38:46 +00:00
fn get(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) anyerror!T {
2022-09-11 08:55:20 +00:00
return switch (self) {
.postgres => |pg| pg.get(T, idx, alloc),
.sqlite => |lite| lite.get(T, idx, alloc),
2022-07-15 00:58:08 +00:00
};
}
2022-09-11 08:55:20 +00:00
};
2022-07-15 00:58:08 +00:00
2022-09-11 11:38:46 +00:00
const DbUnion = union(Type) {
2022-09-11 08:55:20 +00:00
postgres: postgres.Db,
sqlite: sqlite.Db,
2022-09-11 11:38:46 +00:00
};
2022-07-15 00:58:08 +00:00
2022-09-11 11:38:46 +00:00
pub const Db = struct {
underlying: DbUnion,
2022-09-11 08:55:20 +00:00
pub fn open(cfg: Config) !Db {
return switch (cfg) {
.postgres => |postgres_cfg| Db{
2022-09-11 11:38:46 +00:00
.underlying = .{
.postgres = try postgres.Db.open(postgres_cfg.conn_str),
},
2022-09-08 01:55:55 +00:00
},
2022-09-11 08:55:20 +00:00
.sqlite => |lite_cfg| Db{
2022-09-11 11:38:46 +00:00
.underlying = .{
.sqlite = try sqlite.Db.open(lite_cfg.file_path),
},
2022-09-08 01:55:55 +00:00
},
2022-09-11 08:55:20 +00:00
};
}
2022-09-11 11:38:46 +00:00
2022-09-11 08:55:20 +00:00
pub fn close(self: Db) void {
2022-09-11 11:38:46 +00:00
switch (self.underlying) {
2022-09-11 08:55:20 +00:00
.postgres => |pg| pg.close(),
.sqlite => |lite| lite.close(),
2022-09-08 01:55:55 +00:00
}
2022-07-15 00:58:08 +00:00
}
2022-07-22 04:18:20 +00:00
2022-09-11 11:38:46 +00:00
pub fn query(
self: Db,
comptime result_types: []const type,
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
) !Results(result_types) {
// Create fake transaction to use its functions
return (Tx{ .underlying = self.underlying }).query(result_types, sql, args, alloc);
}
pub fn exec(
self: Db,
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
) !void {
// Create fake transaction to use its functions
return (Tx{ .underlying = self.underlying }).exec(sql, args, alloc);
}
pub fn queryRow(
self: Db,
comptime result_types: []const type,
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
) !?Results(result_types).RowTuple {
// Create fake transaction to use its functions
return (Tx{ .underlying = self.underlying }).exec(sql, args, alloc);
}
pub fn insert(
self: Db,
comptime table: []const u8,
value: anytype,
) !void {
// Create fake transaction to use its functions
return (Tx{ .underlying = self.underlying }).insert(table, value);
}
// Begins a transaction
pub fn begin(self: Db) !Tx {
const tx = Tx{ .underlying = self.underlying };
try tx.exec("BEGIN", .{}, null);
return tx;
}
};
pub const Tx = struct {
underlying: DbUnion,
// internal helper fn
fn queryInternal(
self: Tx,
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
) !RawResults {
return switch (self.underlying) {
.postgres => |pg| RawResults{ .postgres = try pg.exec(sql, args, alloc) },
.sqlite => |lite| RawResults{ .sqlite = try lite.exec(sql, args) },
};
}
// Executes a query and returns the result set
pub fn query(
self: Tx,
comptime result_types: []const type,
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
) !Results(result_types) {
return Results(result_types){ .unerlying = try self.queryInternal(sql, args, alloc) };
}
// Executes a query without returning results
pub fn exec(
self: Tx,
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
) !void {
(try self.queryInternal(sql, args, alloc)).finish();
}
// Runs a query and returns a single row
pub fn queryRow(
self: Tx,
comptime result_types: []const type,
q: [:0]const u8,
args: anytype,
alloc: ?Allocator,
) !?Results(result_types).RowTuple {
var results = try self.query(result_types, q, args, alloc);
defer results.finish();
const row = (try results.row(alloc)) orelse return null;
errdefer util.deepFree(alloc, row);
var more_rows = false;
while (try results.row(alloc)) |r| {
util.deepFree(alloc, r);
more_rows = true;
}
if (more_rows) return error.TooManyRows;
return row;
}
// Inserts a single value into a table
pub fn insert(
self: Tx,
comptime table: []const u8,
value: anytype,
) !void {
const ValueType = comptime @TypeOf(value);
const table_spec = comptime table ++ build_field_list(ValueType, null);
const value_spec = comptime build_field_list(ValueType, "?");
const q = comptime std.fmt.comptimePrint(
"INSERT INTO {s} VALUES {s}",
.{ table_spec, value_spec },
);
try self.exec(q, value, null);
}
pub fn rollback(self: Tx) void {
self.exec("ROLLBACK", .{}, null) catch |err| {
std.log.err("Error occured during rollback operation: {}", .{err});
2022-09-11 08:55:20 +00:00
};
2022-07-22 04:18:20 +00:00
}
2022-09-11 11:38:46 +00:00
pub fn commit(self: Tx) !void {
try self.exec("COMMIT", .{}, null);
}
2022-07-15 00:58:08 +00:00
};
2022-09-11 11:38:46 +00:00
fn build_field_list(comptime T: type, comptime placeholder: ?[]const u8) []const u8 {
comptime {
const joiner = ",";
var result: []const u8 = "";
inline for (std.meta.fields(T)) |f| {
result = result ++ joiner ++ (placeholder orelse f.name);
}
return "(" ++ result[joiner.len..] ++ ")";
}
}